aisolate-agent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. aisolate_agent-0.1.0/PKG-INFO +51 -0
  2. aisolate_agent-0.1.0/README.md +26 -0
  3. aisolate_agent-0.1.0/pyproject.toml +38 -0
  4. aisolate_agent-0.1.0/setup.cfg +4 -0
  5. aisolate_agent-0.1.0/src/aisolate/__init__.py +0 -0
  6. aisolate_agent-0.1.0/src/aisolate/agent/__init__.py +0 -0
  7. aisolate_agent-0.1.0/src/aisolate/agent/agent.py +516 -0
  8. aisolate_agent-0.1.0/src/aisolate/agent/config/__init__.py +5 -0
  9. aisolate_agent-0.1.0/src/aisolate/agent/config/agent_settings.py +39 -0
  10. aisolate_agent-0.1.0/src/aisolate/agent/config/prompts/__init__.py +0 -0
  11. aisolate_agent-0.1.0/src/aisolate/agent/core/__init__.py +16 -0
  12. aisolate_agent-0.1.0/src/aisolate/agent/core/context.py +451 -0
  13. aisolate_agent-0.1.0/src/aisolate/agent/core/events.py +103 -0
  14. aisolate_agent-0.1.0/src/aisolate/agent/core/exceptions.py +85 -0
  15. aisolate_agent-0.1.0/src/aisolate/agent/core/protocols.py +270 -0
  16. aisolate_agent-0.1.0/src/aisolate/agent/core/snapshot.py +69 -0
  17. aisolate_agent-0.1.0/src/aisolate/agent/core/tests/test_context_snapshots.py +238 -0
  18. aisolate_agent-0.1.0/src/aisolate/agent/core/tests/test_snapshot.py +217 -0
  19. aisolate_agent-0.1.0/src/aisolate/agent/logging/__init__.py +14 -0
  20. aisolate_agent-0.1.0/src/aisolate/agent/logging/config.py +83 -0
  21. aisolate_agent-0.1.0/src/aisolate/agent/logging/factory.py +141 -0
  22. aisolate_agent-0.1.0/src/aisolate/agent/logging/langfuse_integration.py +164 -0
  23. aisolate_agent-0.1.0/src/aisolate/agent/logging/unified_logger.py +695 -0
  24. aisolate_agent-0.1.0/src/aisolate/agent/main.py +203 -0
  25. aisolate_agent-0.1.0/src/aisolate/agent/memory/__init__.py +10 -0
  26. aisolate_agent-0.1.0/src/aisolate/agent/memory/models.py +66 -0
  27. aisolate_agent-0.1.0/src/aisolate/agent/states/__init__.py +17 -0
  28. aisolate_agent-0.1.0/src/aisolate/agent/states/base.py +157 -0
  29. aisolate_agent-0.1.0/src/aisolate/agent/states/error_recovery.py +63 -0
  30. aisolate_agent-0.1.0/src/aisolate/agent/states/executing.py +293 -0
  31. aisolate_agent-0.1.0/src/aisolate/agent/states/finished.py +42 -0
  32. aisolate_agent-0.1.0/src/aisolate/agent/states/planning.py +128 -0
  33. aisolate_agent-0.1.0/src/aisolate/agent/states/reflection.py +50 -0
  34. aisolate_agent-0.1.0/src/aisolate/agent/strategies/__init__.py +9 -0
  35. aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/__init__.py +9 -0
  36. aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/harmony.py +449 -0
  37. aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/oai.py +572 -0
  38. aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/__init__.py +7 -0
  39. aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/in_memory.py +159 -0
  40. aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/tests/test_in_memory.py +273 -0
  41. aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/vector.py +35 -0
  42. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/__init__.py +33 -0
  43. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/decorators.py +375 -0
  44. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/factory.py +87 -0
  45. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/interface.py +103 -0
  46. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/noop.py +80 -0
  47. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/opentelemetry_impl.py +167 -0
  48. aisolate_agent-0.1.0/src/aisolate/agent/telemetry/utils.py +58 -0
  49. aisolate_agent-0.1.0/src/aisolate/agent/tests/__init__.py +1 -0
  50. aisolate_agent-0.1.0/src/aisolate/agent/tests/conftest.py +154 -0
  51. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_core/__init__.py +1 -0
  52. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_core/test_state_transitions.py +73 -0
  53. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_integration/__init__.py +1 -0
  54. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_integration/test_full_workflow.py +33 -0
  55. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/__init__.py +1 -0
  56. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/test_executing_metadata.py +384 -0
  57. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/test_planning_metadata.py +276 -0
  58. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_strategies/__init__.py +1 -0
  59. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_strategies/test_llm_providers.py +36 -0
  60. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_tools/__init__.py +1 -0
  61. aisolate_agent-0.1.0/src/aisolate/agent/tests/test_tools/test_three_tier_tools.py +42 -0
  62. aisolate_agent-0.1.0/src/aisolate/agent/tools/__init__.py +24 -0
  63. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/__init__.py +13 -0
  64. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/base.py +518 -0
  65. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/final_answer.py +248 -0
  66. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/visit_page_action.py +67 -0
  67. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/web_search_action.py +101 -0
  68. aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/wikipedia_search_action.py +149 -0
  69. aisolate_agent-0.1.0/src/aisolate/agent/tools/base.py +57 -0
  70. aisolate_agent-0.1.0/src/aisolate/agent/tools/execution.py +231 -0
  71. aisolate_agent-0.1.0/src/aisolate/agent/tools/functions/__init__.py +11 -0
  72. aisolate_agent-0.1.0/src/aisolate/agent/tools/functions/base.py +96 -0
  73. aisolate_agent-0.1.0/src/aisolate/agent/tools/models.py +137 -0
  74. aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/__init__.py +13 -0
  75. aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/base.py +51 -0
  76. aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/python_executor.py +273 -0
  77. aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/subagent.py +205 -0
  78. aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/web_search.py +135 -0
  79. aisolate_agent-0.1.0/src/aisolate/agent/tools/routing.py +69 -0
  80. aisolate_agent-0.1.0/src/aisolate/agent/utils.py +97 -0
  81. aisolate_agent-0.1.0/src/aisolate/common/__init__.py +0 -0
  82. aisolate_agent-0.1.0/src/aisolate/common/codec.py +161 -0
  83. aisolate_agent-0.1.0/src/aisolate/common/errors.py +16 -0
  84. aisolate_agent-0.1.0/src/aisolate/common/models.py +74 -0
  85. aisolate_agent-0.1.0/src/aisolate/common/tests/__init__.py +0 -0
  86. aisolate_agent-0.1.0/src/aisolate/common/tests/fixtures.py +160 -0
  87. aisolate_agent-0.1.0/src/aisolate/common/tests/utils.py +117 -0
  88. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/PKG-INFO +51 -0
  89. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/SOURCES.txt +91 -0
  90. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/dependency_links.txt +1 -0
  91. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/entry_points.txt +2 -0
  92. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/requires.txt +17 -0
  93. aisolate_agent-0.1.0/src/aisolate_agent.egg-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: aisolate-agent
3
+ Version: 0.1.0
4
+ Summary: AI Agent with intelligent planning and reasoning
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: openai-harmony>=0.0.8
9
+ Requires-Dist: openai>=1.0.0
10
+ Requires-Dist: jinja2>=3.1.0
11
+ Requires-Dist: pydantic>=2.0.0
12
+ Requires-Dist: pydantic-settings>=2.0.0
13
+ Requires-Dist: aiofiles>=23.0.0
14
+ Requires-Dist: tiktoken>=0.5.0
15
+ Requires-Dist: structlog>=23.2.0
16
+ Requires-Dist: opentelemetry-api>=1.20.0
17
+ Requires-Dist: opentelemetry-sdk>=1.20.0
18
+ Requires-Dist: opentelemetry-instrumentation>=0.41b0
19
+ Requires-Dist: opentelemetry-exporter-otlp>=1.20.0
20
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.41b0
21
+ Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.41b0
22
+ Requires-Dist: langfuse>=2.0.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Requires-Dist: wikipedia-api>=0.6.0
25
+
26
+ # aisolate-agent
27
+
28
+ AI Agent with intelligent planning and reasoning
29
+
30
+ ## Local Installation
31
+
32
+ ```bash
33
+ pip install path/to/aisolate_agent-0.1.0-py3-none-any.whl
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```python
39
+ # Import the main module
40
+ import aisolate.agent
41
+
42
+ # Use the package...
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.10+
48
+
49
+ ## License
50
+
51
+ Apache License 2.0
@@ -0,0 +1,26 @@
1
+ # aisolate-agent
2
+
3
+ AI Agent with intelligent planning and reasoning
4
+
5
+ ## Local Installation
6
+
7
+ ```bash
8
+ pip install path/to/aisolate_agent-0.1.0-py3-none-any.whl
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ # Import the main module
15
+ import aisolate.agent
16
+
17
+ # Use the package...
18
+ ```
19
+
20
+ ## Requirements
21
+
22
+ - Python 3.10+
23
+
24
+ ## License
25
+
26
+ Apache License 2.0
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aisolate-agent"
7
+ version = "0.1.0"
8
+ description = "AI Agent with intelligent planning and reasoning"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "Apache-2.0"}
12
+
13
+ dependencies = [
14
+ "openai-harmony>=0.0.8",
15
+ "openai>=1.0.0",
16
+ "jinja2>=3.1.0",
17
+ "pydantic>=2.0.0",
18
+ "pydantic-settings>=2.0.0",
19
+ "aiofiles>=23.0.0",
20
+ "tiktoken>=0.5.0",
21
+ "structlog>=23.2.0",
22
+ "opentelemetry-api>=1.20.0",
23
+ "opentelemetry-sdk>=1.20.0",
24
+ "opentelemetry-instrumentation>=0.41b0",
25
+ "opentelemetry-exporter-otlp>=1.20.0",
26
+ "opentelemetry-instrumentation-httpx>=0.41b0",
27
+ "opentelemetry-instrumentation-aiohttp-client>=0.41b0",
28
+ "langfuse>=2.0.0",
29
+ "rich>=13.0.0",
30
+ "wikipedia-api>=0.6.0",
31
+ ]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+ include = ["aisolate*"]
36
+
37
+ [project.scripts]
38
+ aisolate-agent = "aisolate.agent.main:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
File without changes
@@ -0,0 +1,516 @@
1
+ """Main Agent class - public API for the three-tier tool architecture."""
2
+
3
+ from typing import Optional, Dict, Any, List, Iterable, TYPE_CHECKING
4
+ import time
5
+ import traceback
6
+
7
+ from .config.agent_settings import AgentSettings
8
+ from .core.context import AgentContext
9
+ from .core.protocols import LLMStrategy, MemoryStrategy
10
+ from .core.snapshot import AgentSnapshot
11
+ from .core.events import (
12
+ AgentStartedEvent,
13
+ TurnCompletedEvent,
14
+ AgentFinishedEvent,
15
+ AgentErrorEvent,
16
+ )
17
+ from openai_harmony import Author, Message, Role, TextContent
18
+ from .states.planning import PlanningState
19
+ from .utils import get_logger, setup_logging
20
+ from .logging import LoggingConfig
21
+
22
+ from .tools.platform.subagent import SubAgentTool
23
+ from .tools.platform.python_executor import PythonExecutorTool
24
+ from .tools.actions.final_answer import FinalAnswerAction
25
+ from .tools.actions.base import validate_action
26
+
27
+ from aisolate.client.sandbox import Sandbox
28
+
29
+ from .tools.models import FinalAnswerResult
30
+
31
+ logger = get_logger("aisolate.agent")
32
+
33
+
34
+ class Agent:
35
+ """Main agent class that orchestrates the three-tier tool architecture.
36
+
37
+ This class provides the public API for creating and running agents with
38
+ different strategies for LLM interaction, memory and tool execution.
39
+
40
+ Example usage:
41
+ ```python
42
+ # Create agent with pre-configured strategies
43
+ harmony_llm = HarmonyLLMStrategy(base_url="https://api.harmony.ai/v1")
44
+ in_memory = InMemoryStrategy()
45
+
46
+ async with Sandbox() as sandbox:
47
+ agent = Agent(
48
+ llm_strategy=harmony_llm,
49
+ memory_strategy=in_memory,
50
+ sandbox=sandbox,
51
+ settings=AgentSettings(max_turns=20),
52
+ actions=[WebSearchAction()]
53
+ )
54
+
55
+ result = await agent.run("What is the weather in San Francisco?")
56
+ print(result)
57
+ ```
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ name: str,
63
+ description: str,
64
+ llm_strategy: LLMStrategy,
65
+ memory_strategy: MemoryStrategy,
66
+ sandbox: Sandbox,
67
+ settings: Optional[AgentSettings] = None,
68
+ managed_agents: Optional[List["Agent"]] = None,
69
+ builtin_tools: Optional[Iterable[Any]] = None,
70
+ function_tools: Optional[Iterable[Any]] = None,
71
+ actions: Optional[List[Any]] = None,
72
+ ):
73
+ """Initialize the Agent with configured strategies.
74
+
75
+ Args:
76
+ llm_strategy: Pre-configured LLM strategy implementation
77
+ memory_strategy: Pre-configured memory strategy implementation
78
+ sandbox: Active sandbox instance for code execution
79
+ settings: Agent-specific configuration settings
80
+ builtin_tools: Optional list of builtin platform tools
81
+ function_tools: Optional list of function tools
82
+ actions: List of action instances
83
+ """
84
+ self.name = name
85
+ self.description = description
86
+ self.llm_strategy = llm_strategy
87
+ self.memory_strategy = memory_strategy
88
+ self.sandbox = sandbox
89
+ self.settings = settings or AgentSettings()
90
+
91
+ # --- Defaults & override merging ---
92
+ def _default_builtin() -> list[Any]:
93
+ python_tool = PythonExecutorTool()
94
+ # Apply strategy-specific tool name override for reserved names
95
+ python_tool.name = self.llm_strategy.get_tool_name("python")
96
+ return [python_tool]
97
+
98
+ if managed_agents is None:
99
+ self.sub_agent_tools = {}
100
+ else:
101
+ self.sub_agent_tools = {}
102
+ for agent in managed_agents:
103
+ # Pass namespace config from parent's LLM strategy (Solution 1)
104
+ sa = SubAgentTool(
105
+ agent=agent,
106
+ namespace_config=self.llm_strategy.namespace_config,
107
+ )
108
+ self.sub_agent_tools[sa.name] = sa
109
+
110
+ if builtin_tools is None:
111
+ self.builtin_tools = _default_builtin()
112
+ else:
113
+ # Merge: user overrides by name and apply strategy-specific name overrides
114
+ base = {t.name: t for t in _default_builtin()}
115
+ for t in builtin_tools:
116
+ # Apply strategy-specific name override
117
+ t.name = self.llm_strategy.get_tool_name(t.name)
118
+ base[t.name] = t
119
+ self.builtin_tools = list(base.values())
120
+
121
+ self.function_tools = list(function_tools or [])
122
+
123
+ # Actions: ensure final_answer exists; convert list to dict using action.name
124
+ base_actions: Dict[str, Any] = {"final_answer": FinalAnswerAction()}
125
+
126
+ if actions:
127
+ # Convert list of actions to dict using action.name as key
128
+ for action in actions:
129
+ if hasattr(action, "name") and action.name:
130
+ base_actions[action.name] = action
131
+ else:
132
+ raise ValueError(
133
+ f"Action {action.__class__.__name__} must have a valid 'name' attribute"
134
+ )
135
+
136
+ # Validate all actions during agent initialization
137
+ validated_actions = {}
138
+ for name, action in base_actions.items():
139
+ try:
140
+ validate_action(action)
141
+ logger.info("Validated action", action_name=name)
142
+ validated_actions[name] = action
143
+ except Exception as e:
144
+ logger.error("Action validation failed", action_name=name, error=str(e))
145
+ raise ValueError(f"Invalid action '{name}': {e}") from e
146
+
147
+ self.actions = validated_actions
148
+
149
+ async def run(self, task: str) -> "FinalAnswerResult":
150
+ """Run the agent on a single task and return the final answer.
151
+
152
+ This is a convenience wrapper around run_streaming() that extracts
153
+ just the final result. Creates a new conversation with a generated UUID.
154
+
155
+ Args:
156
+ task: The task description for the agent to accomplish
157
+
158
+ Returns:
159
+ FinalAnswerResult containing answer text and optional metadata (sources, confidence)
160
+
161
+ Raises:
162
+ RuntimeError: If agent is already running or no final answer provided
163
+ ValueError: If task is empty
164
+ """
165
+ import uuid
166
+
167
+ final_answer: Optional[FinalAnswerResult] = None
168
+
169
+ # Generate new conversation_id for this execution
170
+ conversation_id = str(uuid.uuid4())
171
+
172
+ async for update in self.run_streaming(task, conversation_id):
173
+ if hasattr(update, "type") and update.type == "agent_finished":
174
+ final_answer = getattr(update, "final_answer", None)
175
+ if final_answer is not None:
176
+ return final_answer
177
+ elif hasattr(update, "type") and update.type == "agent_error":
178
+ # Always raise exceptions in the synchronous run() method
179
+ error_msg = getattr(update, "error", "Unknown error")
180
+ raise RuntimeError(f"Agent execution failed: {error_msg}")
181
+
182
+ # If we get here, no final answer was provided
183
+ raise RuntimeError("Agent completed without providing a final answer")
184
+
185
+ async def run_streaming(self, task: str, conversation_id: str):
186
+ """Run the agent with streaming updates.
187
+
188
+ Creates a fresh context-bound sandbox for each execution.
189
+
190
+ Args:
191
+ task: The task description for the agent to accomplish
192
+ conversation_id: Conversation UUID (required)
193
+ - New conversation: caller generates new UUID
194
+ - Existing conversation: caller provides existing UUID
195
+
196
+ Yields:
197
+ Dict updates about agent state, tool calls, and progress
198
+ """
199
+ # Add structured logging context
200
+ bound_logger = logger.bind(
201
+ task=task,
202
+ agent_id=id(self),
203
+ llm_strategy=type(self.llm_strategy).__name__,
204
+ memory_strategy=type(self.memory_strategy).__name__,
205
+ conversation_id=conversation_id,
206
+ )
207
+
208
+ bound_logger.info("Starting agent execution")
209
+
210
+ try:
211
+ # Use new telemetry system for tracing
212
+ from .telemetry import get_tracer
213
+
214
+ tracer = get_tracer()
215
+
216
+ with tracer.span("agent.session") as session_span: # type: ignore
217
+ session_span.set_attribute("agent.task", task)
218
+ session_span.set_attribute(
219
+ "agent.llm_strategy", type(self.llm_strategy).__name__
220
+ )
221
+ session_span.set_attribute(
222
+ "agent.memory_strategy", type(self.memory_strategy).__name__
223
+ )
224
+ session_span.set_attribute("agent.max_turns", self.settings.max_turns)
225
+
226
+ # Create fresh context-bound sandbox for this execution
227
+ sandbox_ctx = await self.sandbox.acreate_context_bound_sandbox()
228
+
229
+ # Create fresh AgentContext for this execution
230
+ context = AgentContext(
231
+ agent_name=self.name,
232
+ task=task,
233
+ llm=self.llm_strategy,
234
+ memory=self.memory_strategy,
235
+ sandbox=sandbox_ctx,
236
+ max_turns=self.settings.max_turns,
237
+ conversation_id=conversation_id,
238
+ sub_agent_tools=self.sub_agent_tools,
239
+ actions=self.actions,
240
+ builtin_tools=self.builtin_tools,
241
+ function_tools=self.function_tools,
242
+ parent_context=None, # Top-level agent
243
+ )
244
+
245
+ # RESTORATION LOGIC - Load and apply snapshot if it exists
246
+ snapshot = await context.memory.load_latest_snapshot(conversation_id)
247
+ if snapshot:
248
+ self._restore_context_from_snapshot(context, snapshot)
249
+ bound_logger.info(
250
+ "Restored from snapshot",
251
+ turn=snapshot.turn,
252
+ messages_count=len(snapshot.conversation_history),
253
+ )
254
+ else:
255
+ bound_logger.debug("No snapshot found, starting fresh")
256
+
257
+ # Execute with prepared context
258
+ async for result in self._execute_with_context(
259
+ context, bound_logger, tracer
260
+ ):
261
+ yield result
262
+ except Exception as e:
263
+ import traceback
264
+
265
+ # Log with full stack trace
266
+ bound_logger.error(
267
+ "Agent execution failed (outer catch)",
268
+ error=str(e),
269
+ exception=type(e).__name__,
270
+ traceback=traceback.format_exc(),
271
+ )
272
+
273
+ # Also use structured exception logging
274
+ bound_logger.log_exception(e, "agent_execution_outer")
275
+
276
+ yield AgentErrorEvent(
277
+ error=str(e),
278
+ exception_type=type(e).__name__,
279
+ traceback=(
280
+ traceback.format_exc() if self.settings.debug_mode else None
281
+ ),
282
+ turn=0,
283
+ )
284
+ if self.settings.debug_mode:
285
+ raise
286
+
287
+ def _restore_context_from_snapshot(
288
+ self, context: AgentContext, snapshot: "AgentSnapshot"
289
+ ) -> None:
290
+ """Restore context from snapshot with smart state handling.
291
+
292
+ - If snapshot.finished = True: Reset to Planning (new task on completed conversation)
293
+ - If snapshot.finished = False: Preserve turn for resumption
294
+
295
+ Args:
296
+ context: Fresh context to restore into
297
+ snapshot: Snapshot with saved state
298
+ """
299
+ from openai_harmony import Message
300
+ from .states.planning import PlanningState
301
+
302
+ # Restore conversation history (using from_dict because messages are serialized with to_dict())
303
+ messages = [
304
+ Message.from_dict(msg_data) for msg_data in snapshot.conversation_history
305
+ ]
306
+ context.set_conversation_history(messages)
307
+
308
+ # Restore execution state
309
+ context.execution.turn = snapshot.turn
310
+
311
+ # Always reset execution.finished to allow continuation
312
+ context.execution.finished = False
313
+
314
+ # Smart state restoration: reset to Planning if conversation was completed
315
+ if snapshot.finished:
316
+ # Completed conversation: start fresh with new planning
317
+ context.transition_to(PlanningState())
318
+ logger.info(
319
+ "Restored completed conversation, reset to Planning",
320
+ turn=snapshot.turn,
321
+ conversation_id=snapshot.conversation_id,
322
+ )
323
+
324
+ # Restore final answer if present (for context/display)
325
+ # if snapshot.final_answer:
326
+ # from .tools.models import FinalAnswerResult
327
+
328
+ # context._final_answer = FinalAnswerResult(**snapshot.final_answer)
329
+
330
+ logger.debug(
331
+ "Context restored from snapshot",
332
+ turn=snapshot.turn,
333
+ messages_count=len(messages),
334
+ had_final_answer=bool(snapshot.final_answer),
335
+ )
336
+
337
+ async def _execute_with_context(self, context: AgentContext, bound_logger, tracer):
338
+ """Execute agent with given context.
339
+
340
+ Context is assumed to be ready (fresh or restored).
341
+ NO restoration logic here - that happens in run_streaming().
342
+
343
+ Args:
344
+ context: Fresh AgentContext with isolated sandbox (possibly restored)
345
+ bound_logger: Logger with bound context
346
+ tracer: Telemetry tracer
347
+ """
348
+ start_time = time.time()
349
+ try:
350
+ # Initialize remote python executor with actions
351
+ await context.remote_python_executor.send_actions(self.actions)
352
+
353
+ # Initialize conversation history with LLM strategy (only if not restored)
354
+ # if not context.get_conversation_messages():
355
+ initial_messages = self.llm_strategy.prepare_initial_history(context)
356
+ context.initialize_conversation(initial_messages)
357
+ # else:
358
+ # context.initialize_conversation(
359
+ # [Message.from_role_and_content(Role.USER, context.task)]
360
+ # )
361
+
362
+ # Set initial state (only if not restored)
363
+ if context.execution.current_state is None:
364
+ context.transition_to(PlanningState())
365
+
366
+ yield AgentStartedEvent(
367
+ task=context.task,
368
+ turn=context.turn,
369
+ max_turns=self.settings.max_turns,
370
+ agent_name=self.name,
371
+ conversation_id=context.conversation_id,
372
+ )
373
+
374
+ # Execute agent turns until completion
375
+ while not context.is_finished:
376
+ # Create tracing span for individual turn (always - no conditional logic)
377
+ with tracer.span(f"agent.turn.{context.turn}") as turn_span: # type: ignore
378
+ turn_span.set_attribute("agent.turn", context.turn)
379
+ turn_span.set_attribute(
380
+ "agent.state", type(context._state).__name__
381
+ )
382
+ turn_span.set_attribute("agent.task", context.task)
383
+
384
+ turn_result = await context.execute_turn()
385
+
386
+ # Create TurnCompletedEvent with runtime metadata
387
+ turn_event = TurnCompletedEvent(
388
+ turn=context.turn,
389
+ state=turn_result.state,
390
+ is_finished=context.is_finished,
391
+ final_answer=context.final_answer,
392
+ state_metadata=turn_result.state_metadata,
393
+ )
394
+
395
+ # Persist snapshot if conversation_id exists
396
+ if context.conversation_id:
397
+ snapshot = context.create_snapshot()
398
+ snapshot.add_event(
399
+ turn_event
400
+ ) # Store event with runtime metadata
401
+ await context.memory.save_snapshot(snapshot)
402
+
403
+ turn_span.set_attribute("turn.completed", True)
404
+ turn_span.set_attribute(
405
+ "turn.final_answer_provided",
406
+ bool(context.final_answer),
407
+ )
408
+ turn_span.set_status("OK")
409
+
410
+ # Yield event for streaming
411
+ yield turn_event
412
+
413
+ if context.turn >= self.settings.max_turns:
414
+ bound_logger.warning(
415
+ "Reached max turns", max_turns=self.settings.max_turns
416
+ )
417
+ break
418
+
419
+ # Log completion with proper FinalAnswerResult handling
420
+ if context.final_answer:
421
+ bound_logger.info(
422
+ "Agent execution completed",
423
+ total_turns=context.turn,
424
+ final_answer_length=len(context.final_answer.answer),
425
+ has_sources=context.final_answer.sources is not None,
426
+ has_confidence=context.final_answer.confidence is not None,
427
+ )
428
+ else:
429
+ bound_logger.info(
430
+ "Agent execution completed",
431
+ total_turns=context.turn,
432
+ final_answer=None,
433
+ )
434
+
435
+ yield AgentFinishedEvent(
436
+ final_answer=context.final_answer,
437
+ total_turns=context.turn,
438
+ execution_time=time.time() - start_time,
439
+ )
440
+
441
+ except Exception as e:
442
+ import traceback
443
+
444
+ # Log with full stack trace
445
+ bound_logger.error(
446
+ "Agent execution failed",
447
+ error=str(e),
448
+ exception=type(e).__name__,
449
+ traceback=traceback.format_exc(),
450
+ )
451
+
452
+ # Also log the exception for structured logging
453
+ bound_logger.log_exception(e, "agent_execution")
454
+ yield AgentErrorEvent(
455
+ error=str(e),
456
+ turn=context.turn if context else 0,
457
+ exception_type=type(e).__name__,
458
+ traceback=traceback.format_exc() if self.settings.debug_mode else None,
459
+ )
460
+ if self.settings.debug_mode:
461
+ raise
462
+
463
+ def get_conversation_history(self) -> List[Any]:
464
+ """Get the current conversation history.
465
+
466
+ Note: This method is deprecated as Agent no longer maintains instance state.
467
+ Context is created fresh for each execution.
468
+
469
+ Returns:
470
+ Empty list (no persistent context)
471
+ """
472
+ return []
473
+
474
+ def get_memory(self) -> List[Dict[str, Any]]:
475
+ """Get the agent's long-term memory.
476
+
477
+ Note: This method is deprecated as Agent no longer maintains instance state.
478
+ Context is created fresh for each execution.
479
+
480
+ Returns:
481
+ Empty list (no persistent context)
482
+ """
483
+ return []
484
+
485
+ def is_running(self) -> bool:
486
+ """Check if the agent is currently running.
487
+
488
+ Note: This method is deprecated as Agent no longer tracks running state.
489
+ Each execution creates its own context.
490
+
491
+ Returns:
492
+ False (no persistent running state)
493
+ """
494
+ return False
495
+
496
+ def get_current_state(self) -> Optional[str]:
497
+ """Get the name of the current agent state.
498
+
499
+ Note: This method is deprecated as Agent no longer maintains instance state.
500
+ Context is created fresh for each execution.
501
+
502
+ Returns:
503
+ None (no persistent context)
504
+ """
505
+ return None
506
+
507
+ def get_turn_count(self) -> int:
508
+ """Get the current turn count.
509
+
510
+ Note: This method is deprecated as Agent no longer maintains instance state.
511
+ Context is created fresh for each execution.
512
+
513
+ Returns:
514
+ 0 (no persistent context)
515
+ """
516
+ return 0
@@ -0,0 +1,5 @@
1
+ """Configuration module for agent settings."""
2
+
3
+ from .agent_settings import AgentSettings
4
+
5
+ __all__ = ["AgentSettings"]
@@ -0,0 +1,39 @@
1
+ """Agent configuration settings."""
2
+
3
+ from typing import Dict, Any, Optional
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+ from pydantic import Field
6
+
7
+
8
+ class AgentSettings(BaseSettings):
9
+ """Core agent-specific configuration only.
10
+
11
+ This class focuses solely on agent behaviors rather than strategy configuration.
12
+ Strategies are configured directly when instantiated.
13
+ """
14
+
15
+ max_turns: int = Field(
16
+ default=50, description="Maximum number of execution turns before agent stops"
17
+ )
18
+ reflection_turns: int = Field(
19
+ default=5, description="Maximum number of execution turns before agent reflects"
20
+ )
21
+ enable_error_recovery: bool = Field(
22
+ default=True, description="Whether to enable automatic error recovery"
23
+ )
24
+
25
+ max_recovery_attempts: int = Field(
26
+ default=3,
27
+ description="Maximum number of recovery attempts for failed operations",
28
+ )
29
+
30
+ conversation_history_max_length: Optional[int] = Field(
31
+ default=None,
32
+ description="Maximum number of messages to keep in conversation history",
33
+ )
34
+
35
+ debug_mode: bool = Field(
36
+ default=False, description="Enable debug logging and additional validation"
37
+ )
38
+
39
+ model_config = SettingsConfigDict(env_prefix="AGENT_", case_sensitive=False)