aisolate-agent 0.3.2__tar.gz → 0.3.4__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 (213) hide show
  1. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/PKG-INFO +2 -2
  2. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/README.md +1 -1
  3. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/pyproject.toml +1 -1
  4. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/agent.py +57 -17
  5. aisolate_agent-0.3.4/src/aisolate/agent/config/__init__.py +15 -0
  6. aisolate_agent-0.3.4/src/aisolate/agent/config/state_llm.py +92 -0
  7. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/context.py +16 -0
  8. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/events.py +2 -0
  9. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/protocols.py +8 -0
  10. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/task.py +3 -3
  11. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/llm/base.py +44 -6
  12. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/llm/harmony.py +4 -4
  13. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/llm/oai.py +52 -13
  14. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/llm/qwen.py +22 -4
  15. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/conftest.py +3 -0
  16. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_event_agent_name_stamping.py +3 -0
  17. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_run_streaming_cancel.py +3 -0
  18. aisolate_agent-0.3.4/src/aisolate/agent/tests/test_core/test_state_llm_overrides.py +421 -0
  19. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_turn_started_events.py +3 -0
  20. aisolate_agent-0.3.4/src/aisolate/agent/tests/test_integration/test_prompt_template_contract.py +120 -0
  21. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_llm_generate_span.py +8 -1
  22. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_subagent_failure_contract.py +9 -1
  23. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/subagent.py +15 -0
  24. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/PKG-INFO +2 -2
  25. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/SOURCES.txt +3 -0
  26. aisolate_agent-0.3.2/src/aisolate/agent/config/__init__.py +0 -5
  27. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/setup.cfg +0 -0
  28. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/__init__.py +0 -0
  29. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/__init__.py +0 -0
  30. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/config/agent_settings.py +0 -0
  31. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/config/prompts/__init__.py +0 -0
  32. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/__init__.py +0 -0
  33. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/artifacts.py +0 -0
  34. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/budget.py +0 -0
  35. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/conversation_log.py +0 -0
  36. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/exceptions.py +0 -0
  37. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/graph.py +0 -0
  38. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/initial_state.py +0 -0
  39. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/llm_response.py +0 -0
  40. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/policy.py +0 -0
  41. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/snapshot.py +0 -0
  42. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/spine_compaction.py +0 -0
  43. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/tests/test_context_snapshots.py +0 -0
  44. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/tests/test_snapshot.py +0 -0
  45. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/views.py +0 -0
  46. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/core/workspace.py +0 -0
  47. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/graphs.py +0 -0
  48. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/logging/__init__.py +0 -0
  49. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/logging/config.py +0 -0
  50. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/logging/factory.py +0 -0
  51. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/logging/unified_logger.py +0 -0
  52. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/main.py +0 -0
  53. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/memory/__init__.py +0 -0
  54. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/memory/models.py +0 -0
  55. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/__init__.py +0 -0
  56. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/base.py +0 -0
  57. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/clarification.py +0 -0
  58. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/compaction.py +0 -0
  59. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/critic.py +0 -0
  60. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/executing.py +0 -0
  61. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/expert_synthesis.py +0 -0
  62. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/finished.py +0 -0
  63. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/hitl_pause.py +0 -0
  64. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/outputs.py +0 -0
  65. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/states/planning.py +0 -0
  66. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/status.py +0 -0
  67. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/__init__.py +0 -0
  68. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/llm/__init__.py +0 -0
  69. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/memory/__init__.py +0 -0
  70. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/memory/in_memory.py +0 -0
  71. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/memory/tests/test_in_memory.py +0 -0
  72. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/strategies/memory/vector.py +0 -0
  73. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/substrate.py +0 -0
  74. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/__init__.py +0 -0
  75. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/decorators.py +0 -0
  76. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/factory.py +0 -0
  77. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/interface.py +0 -0
  78. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/noop.py +0 -0
  79. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/openinference.py +0 -0
  80. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/opentelemetry_impl.py +0 -0
  81. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/telemetry/utils.py +0 -0
  82. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/__init__.py +0 -0
  83. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/fakes.py +0 -0
  84. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/__init__.py +0 -0
  85. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_architecture_assumptions.py +0 -0
  86. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_artifact_rows.py +0 -0
  87. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_change_bridge_echo.py +0 -0
  88. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_conversation_log.py +0 -0
  89. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_delegated_lineage_restore.py +0 -0
  90. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_execute_turn_review_pending.py +0 -0
  91. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_firewall_property.py +0 -0
  92. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_graph.py +0 -0
  93. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_initial_state.py +0 -0
  94. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_legacy_snapshot.py +0 -0
  95. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_live_capture.py +0 -0
  96. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_locale_prelude.py +0 -0
  97. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_policy.py +0 -0
  98. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_restore_review_posture.py +0 -0
  99. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_resume_semantics.py +0 -0
  100. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_spine_compaction.py +0 -0
  101. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_state_transitions.py +0 -0
  102. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_status_summarizer.py +0 -0
  103. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_subagent_graph.py +0 -0
  104. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_substrate.py +0 -0
  105. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_token_accumulation.py +0 -0
  106. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_turn_usage_isolation.py +0 -0
  107. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_uniform_state.py +0 -0
  108. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_views.py +0 -0
  109. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_workspace_bridge_attribution.py +0 -0
  110. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_workspace_capture.py +0 -0
  111. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_core/test_workspace_changed_event.py +0 -0
  112. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/__init__.py +0 -0
  113. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/conftest.py +0 -0
  114. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_delegation_file_paths.py +0 -0
  115. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_delegation_recovery.py +0 -0
  116. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_locale_helpers.py +0 -0
  117. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_size_compaction.py +0 -0
  118. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_stall_clone_96919cf1.py +0 -0
  119. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_truncation_wedge_5b90b101.py +0 -0
  120. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_e2e/test_workspace_lifecycle.py +0 -0
  121. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/__init__.py +0 -0
  122. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/helpers.py +0 -0
  123. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/test_flagship_loop.py +0 -0
  124. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/test_followup_roundtrips.py +0 -0
  125. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/test_full_workflow.py +0 -0
  126. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/test_restore_roundtrips.py +0 -0
  127. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_integration/test_stalled_and_acceptance.py +0 -0
  128. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/__init__.py +0 -0
  129. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_compact_for_llm.py +0 -0
  130. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_critic_enforcer.py +0 -0
  131. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_critic_review_cap.py +0 -0
  132. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_emission_map.py +0 -0
  133. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_executing_metadata.py +0 -0
  134. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_executing_prefix_stability.py +0 -0
  135. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_executing_review_routing.py +0 -0
  136. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_planning_artifact.py +0 -0
  137. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_planning_metadata.py +0 -0
  138. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_progress_ledger.py +0 -0
  139. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_selection.py +0 -0
  140. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_size_compaction_wiring.py +0 -0
  141. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_states/test_stall_recovery.py +0 -0
  142. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/__init__.py +0 -0
  143. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_identity_block.py +0 -0
  144. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_llm_providers.py +0 -0
  145. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_llm_watchdog.py +0 -0
  146. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_prepare_history_contract.py +0 -0
  147. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_qwen_retry.py +0 -0
  148. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_qwen_strategy.py +0 -0
  149. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_qwen_truncation.py +0 -0
  150. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_qwen_workspace_envelope.py +0 -0
  151. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_scripted_llm_contract.py +0 -0
  152. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_telemetry_attributes.py +0 -0
  153. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_strategies/test_turn_budget_block.py +0 -0
  154. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/__init__.py +0 -0
  155. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_bundle_and_restore.py +0 -0
  156. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_delegation_identity.py +0 -0
  157. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_edit_blocks.py +0 -0
  158. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_fake_executor.py +0 -0
  159. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_handover_sidecars.py +0 -0
  160. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_load_handover_resolution.py +0 -0
  161. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_output_refs.py +0 -0
  162. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_sandbox_import_surface.py +0 -0
  163. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_send_actions_error.py +0 -0
  164. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_shell_executing_loop.py +0 -0
  165. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_shell_tools.py +0 -0
  166. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_subagent_handover.py +0 -0
  167. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_three_tier_tools.py +0 -0
  168. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_workspace_actions.py +0 -0
  169. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tests/test_tools/test_workspace_tools.py +0 -0
  170. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/__init__.py +0 -0
  171. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/__init__.py +0 -0
  172. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/base.py +0 -0
  173. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/final_answer.py +0 -0
  174. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/load_handover.py +0 -0
  175. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/visit_page_action.py +0 -0
  176. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/web_search_action.py +0 -0
  177. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/wikipedia_search_action.py +0 -0
  178. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/actions/workspace.py +0 -0
  179. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/base.py +0 -0
  180. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/execution.py +0 -0
  181. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/functions/__init__.py +0 -0
  182. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/functions/base.py +0 -0
  183. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/functions/hitl.py +0 -0
  184. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/models.py +0 -0
  185. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/__init__.py +0 -0
  186. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/base.py +0 -0
  187. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/edit_blocks.py +0 -0
  188. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/files_ops.py +0 -0
  189. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/python_executor.py +0 -0
  190. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/shell.py +0 -0
  191. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/web_search.py +0 -0
  192. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/platform/workspace_tools.py +0 -0
  193. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/tools/routing.py +0 -0
  194. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/trace.py +0 -0
  195. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/agent/utils.py +0 -0
  196. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/__init__.py +0 -0
  197. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/codec.py +0 -0
  198. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/errors.py +0 -0
  199. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/models.py +0 -0
  200. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/output_window.py +0 -0
  201. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/__init__.py +0 -0
  202. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/fake_vm.py +0 -0
  203. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/fixtures.py +0 -0
  204. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/test_codec_channel.py +0 -0
  205. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/test_output_window.py +0 -0
  206. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/common/tests/utils.py +0 -0
  207. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/logging/__init__.py +0 -0
  208. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/logging/formatters.py +0 -0
  209. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate/logging/processors.py +0 -0
  210. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/dependency_links.txt +0 -0
  211. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/entry_points.txt +0 -0
  212. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/requires.txt +0 -0
  213. {aisolate_agent-0.3.2 → aisolate_agent-0.3.4}/src/aisolate_agent.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aisolate-agent
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: AI Agent with intelligent planning and reasoning
5
5
  License: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -30,7 +30,7 @@ AI Agent with intelligent planning and reasoning
30
30
  ## Local Installation
31
31
 
32
32
  ```bash
33
- pip install path/to/aisolate_agent-0.3.2-py3-none-any.whl
33
+ pip install path/to/aisolate_agent-0.3.4-py3-none-any.whl
34
34
  ```
35
35
 
36
36
  ## Usage
@@ -5,7 +5,7 @@ AI Agent with intelligent planning and reasoning
5
5
  ## Local Installation
6
6
 
7
7
  ```bash
8
- pip install path/to/aisolate_agent-0.3.2-py3-none-any.whl
8
+ pip install path/to/aisolate_agent-0.3.4-py3-none-any.whl
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aisolate-agent"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "AI Agent with intelligent planning and reasoning"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,7 @@
1
1
  """Main Agent class - public API for the three-tier tool architecture."""
2
2
 
3
- from typing import Optional, Dict, Any, List, Iterable, TYPE_CHECKING
3
+ from types import MappingProxyType
4
+ from typing import Optional, Dict, Any, List, Iterable, Mapping, TYPE_CHECKING
4
5
  import asyncio
5
6
  import base64
6
7
  import contextlib
@@ -11,6 +12,11 @@ from datetime import datetime, timezone
11
12
  import aiohttp
12
13
 
13
14
  from .config.agent_settings import AgentSettings
15
+ from .config.state_llm import (
16
+ StateLLMOverride,
17
+ resolve_effective_strategy,
18
+ validate_state_llm_overrides,
19
+ )
14
20
  from .core.context import AgentContext
15
21
  from .core.graph import AgentGraph
16
22
  from .core.protocols import LLMStrategy, MemoryStrategy
@@ -136,7 +142,7 @@ class Agent:
136
142
  actions: Optional[List[Any]] = None,
137
143
  checkpoint_storage: Optional[Any] = None,
138
144
  graph: Optional[AgentGraph] = None,
139
- state_model_overrides: Optional[Dict[str, str]] = None,
145
+ state_llm_overrides: Optional[Mapping[str, StateLLMOverride]] = None,
140
146
  file_resolver: Optional[Any] = None,
141
147
  status_llm: Optional[LLMStrategy] = None,
142
148
  status_enter_labels: Optional[Dict[str, str]] = None,
@@ -163,9 +169,11 @@ class Agent:
163
169
  ``settings.enable_clarification``. Review is a fact of the
164
170
  graph — an agent reviews its answers iff the graph has a
165
171
  ``critic`` node.
166
- state_model_overrides: Map of graph node name → model name.
167
- Before each turn, the agent swaps ``context.llm`` to a
168
- strategy configured for the overridden model.
172
+ state_llm_overrides: Map of graph node name → ``StateLLMOverride``
173
+ (model, full strategy, and/or reasoning effort). Validated at
174
+ construction against every graph this agent can execute;
175
+ before each turn, ``apply_state_llm_override`` installs the
176
+ node's effective strategy and effort.
169
177
  file_resolver: Optional FileResolver for resolving file URLs
170
178
  to bytes (used by the critic for image content).
171
179
  status_enter_labels: node→text mapping for the deterministic
@@ -190,7 +198,19 @@ class Agent:
190
198
  self.graph = graph or build_workflow(
191
199
  clarification=self.settings.enable_clarification
192
200
  )
193
- self.state_model_overrides = state_model_overrides or {}
201
+ # Per-node LLM overrides: validated here against every graph this
202
+ # agent can execute — its own, plus the lean sub graph that
203
+ # SubAgentTool forces onto delegates — then resolved once, so runtime
204
+ # reuses exactly the strategy objects that were validated.
205
+ _overrides = dict(state_llm_overrides or {})
206
+ validate_state_llm_overrides(
207
+ llm_strategy, _overrides, [self.graph, build_workflow(role="sub")]
208
+ )
209
+ self.state_llm_overrides = MappingProxyType(_overrides)
210
+ self._effective_llm = {
211
+ node: resolve_effective_strategy(llm_strategy, override)
212
+ for node, override in _overrides.items()
213
+ }
194
214
  self.file_resolver = file_resolver
195
215
  # Optional cheap side model that turns each turn into a one-line
196
216
  # user-facing status update. None disables the feature entirely.
@@ -277,6 +297,22 @@ class Agent:
277
297
  # per Agent instantiation.
278
298
  _validate_managed_agents_acyclic(self)
279
299
 
300
+ def apply_state_llm_override(self, context: "AgentContext") -> None:
301
+ """Install the current node's effective LLM configuration.
302
+
303
+ Called immediately before every paid turn — by ``run_streaming`` and
304
+ by ``SubAgentTool._execute_subagent`` — AFTER the node's ``on_enter``
305
+ installed its default hints, so an explicit override wins. A node
306
+ without an override runs the base strategy; a configured key absent
307
+ from the executing graph is skipped (valid on the agent's other
308
+ graph shape).
309
+ """
310
+ node = context.current_node
311
+ context.llm = self._effective_llm.get(node, self.llm_strategy)
312
+ override = self.state_llm_overrides.get(node)
313
+ if override is not None and override.reasoning_effort is not None:
314
+ context.set_state_hint("reasoning_effort", override.reasoning_effort)
315
+
280
316
  async def run(self, task: str) -> "FinalAnswerResult":
281
317
  """Run the agent on a single task and return the final answer.
282
318
 
@@ -1739,17 +1775,20 @@ class Agent:
1739
1775
  )
1740
1776
  turn_span.set_attribute("agent.task", context.task)
1741
1777
 
1742
- # Swap LLM strategy if the current node has a model override.
1743
- # Overrides can be a model name (str) or a full strategy object.
1744
- override = self.state_model_overrides.get(context.current_node)
1745
- if override is not None:
1746
- if isinstance(override, str):
1747
- context.llm = self.llm_strategy.with_model(override)
1748
- else:
1749
- # Full strategy object (e.g. with_model(...).with_templates(...))
1750
- context.llm = override
1751
- else:
1752
- context.llm = self.llm_strategy
1778
+ # Install the node's effective LLM configuration. Sits
1779
+ # before the retry loop so transient retries of this turn
1780
+ # keep one configuration; the next iteration re-resolves
1781
+ # because the turn may have routed to a different node.
1782
+ self.apply_state_llm_override(context)
1783
+ turn_span.set_attribute(
1784
+ "agent.llm.model",
1785
+ getattr(context.llm, "model", None) or "unknown",
1786
+ )
1787
+ _applied_effort = context.llm.applied_reasoning_effort(context)
1788
+ if _applied_effort is not None:
1789
+ turn_span.set_attribute(
1790
+ "agent.reasoning_effort", _applied_effort
1791
+ )
1753
1792
 
1754
1793
  # Two retry classes wrap the turn: transient API errors
1755
1794
  # (timeout, rate-limit, server error) so a network blip
@@ -1842,6 +1881,7 @@ class Agent:
1842
1881
  reasoning=turn_result.reasoning,
1843
1882
  model=getattr(context.llm, "model", None),
1844
1883
  component=context.last_turn_component,
1884
+ reasoning_effort=context.last_applied_reasoning_effort,
1845
1885
  agent_name=self.name,
1846
1886
  )
1847
1887
 
@@ -0,0 +1,15 @@
1
+ """Configuration module for agent settings."""
2
+
3
+ from .agent_settings import AgentSettings
4
+ from .state_llm import (
5
+ StateLLMOverride,
6
+ resolve_effective_strategy,
7
+ validate_state_llm_overrides,
8
+ )
9
+
10
+ __all__ = [
11
+ "AgentSettings",
12
+ "StateLLMOverride",
13
+ "resolve_effective_strategy",
14
+ "validate_state_llm_overrides",
15
+ ]
@@ -0,0 +1,92 @@
1
+ """Per-graph-node LLM execution overrides.
2
+
3
+ ``StateLLMOverride`` selects the model or full strategy and/or the reasoning
4
+ effort for one graph node. ``validate_state_llm_overrides`` is the public,
5
+ side-effect-free validator: ``Agent.__init__`` calls it, and a consuming
6
+ application's CI can call it against a versioned profile table without
7
+ constructing an ``Agent``. Rationale and contract:
8
+ ``docs/architecture/state-llm-profiles.analysis.md``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import List, Mapping, Optional, Sequence, TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from ..core.graph import AgentGraph
18
+ from ..strategies.llm.base import BaseLLMStrategy
19
+
20
+ __all__ = [
21
+ "StateLLMOverride",
22
+ "resolve_effective_strategy",
23
+ "validate_state_llm_overrides",
24
+ ]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class StateLLMOverride:
29
+ """LLM execution override for one graph node.
30
+
31
+ Fields are independently optional: ``model`` shallow-clones the agent's
32
+ base strategy onto another model (same client/endpoint), ``strategy``
33
+ substitutes a full strategy object (different templates, endpoint, or
34
+ provider), and ``reasoning_effort`` overrides the hint installed by the
35
+ state's ``on_enter``. An omitted field inherits the current default.
36
+ """
37
+
38
+ model: Optional[str] = None
39
+ strategy: Optional["BaseLLMStrategy"] = None
40
+ reasoning_effort: Optional[str] = None
41
+
42
+ def __post_init__(self) -> None:
43
+ if self.model is not None and self.strategy is not None:
44
+ raise ValueError("Set model or strategy, not both")
45
+
46
+
47
+ def resolve_effective_strategy(
48
+ base: "BaseLLMStrategy", override: Optional[StateLLMOverride]
49
+ ) -> "BaseLLMStrategy":
50
+ """The strategy a node executes with under ``override`` (pure)."""
51
+ if override is None:
52
+ return base
53
+ if override.strategy is not None:
54
+ return override.strategy
55
+ if override.model is not None:
56
+ return base.with_model(override.model)
57
+ return base
58
+
59
+
60
+ def validate_state_llm_overrides(
61
+ base_strategy: "BaseLLMStrategy",
62
+ overrides: Mapping[str, StateLLMOverride],
63
+ graphs: Sequence["AgentGraph"],
64
+ ) -> None:
65
+ """Validate a full override mapping; raise one error naming every problem.
66
+
67
+ Pure and offline: resolves each entry's effective strategy and asks it to
68
+ validate the effort override, so effort support is checked against the
69
+ strategy/model that will actually execute. ``graphs`` is every graph the
70
+ agent can run; a key must name a node in at least one of them.
71
+ """
72
+ known_nodes: set = set()
73
+ for graph in graphs:
74
+ known_nodes.update(graph.nodes)
75
+
76
+ problems: List[str] = []
77
+ for node, override in overrides.items():
78
+ if node not in known_nodes:
79
+ problems.append(
80
+ f"{node!r}: unknown graph node (known: {sorted(known_nodes)})"
81
+ )
82
+ if override.reasoning_effort is not None:
83
+ resolved = resolve_effective_strategy(base_strategy, override)
84
+ try:
85
+ resolved.validate_reasoning_effort_override(
86
+ override.reasoning_effort
87
+ )
88
+ except ValueError as exc:
89
+ problems.append(f"{node!r}: {exc}")
90
+
91
+ if problems:
92
+ raise ValueError("Invalid state_llm_overrides: " + "; ".join(problems))
@@ -224,6 +224,7 @@ class AgentContext:
224
224
  self._last_turn_usage: Optional[TokenUsage] = None
225
225
  self._last_turn_component: Optional[str] = None
226
226
  self._last_turn_reasoning: Optional[str] = None
227
+ self._last_applied_reasoning_effort: Optional[str] = None
227
228
 
228
229
  # Kernel state checkpoint tracking
229
230
  self.checkpoint_key: Optional[str] = None
@@ -640,6 +641,20 @@ class AgentContext:
640
641
  """Component that acted in the most recent turn (main_agent/critic)."""
641
642
  return self._last_turn_component
642
643
 
644
+ def set_last_applied_reasoning_effort(self, effort: Optional[str]) -> None:
645
+ """Record the effort the strategy applied to this turn's LLM call.
646
+
647
+ Set by ``BaseLLMStrategy.generate_response`` at call time — post-turn
648
+ the graph has already transitioned, so hints no longer describe this
649
+ turn.
650
+ """
651
+ self._last_applied_reasoning_effort = effort
652
+
653
+ @property
654
+ def last_applied_reasoning_effort(self) -> Optional[str]:
655
+ """Applied effort of the most recent turn's own LLM call, if any."""
656
+ return self._last_applied_reasoning_effort
657
+
643
658
  def set_last_reasoning(self, reasoning: Optional[str]) -> None:
644
659
  """Store the reasoning text from the most recent LLM call."""
645
660
  self._last_turn_reasoning = reasoning
@@ -1063,6 +1078,7 @@ class AgentContext:
1063
1078
  self._last_turn_usage = None
1064
1079
  self._last_turn_component = None
1065
1080
  self._last_turn_reasoning = None
1081
+ self._last_applied_reasoning_effort = None
1066
1082
 
1067
1083
  # Capture the node identity BEFORE execution (routing moves it during
1068
1084
  # handle()); the node name is the telemetry key — per-turn usage reads
@@ -185,6 +185,7 @@ class TurnCompletedEvent(AgentEvent):
185
185
  reasoning: Optional[str] = None,
186
186
  model: Optional[str] = None,
187
187
  component: Optional[str] = None,
188
+ reasoning_effort: Optional[str] = None,
188
189
  agent_name: str = "",
189
190
  sub_conversation_id: Optional[str] = None,
190
191
  delegation_id: Optional[str] = None,
@@ -201,6 +202,7 @@ class TurnCompletedEvent(AgentEvent):
201
202
  # Which component acted this turn ("main_agent"/"critic"), so
202
203
  # consumers can attribute the turn's usage per component live.
203
204
  self.component = component
205
+ self.reasoning_effort = reasoning_effort
204
206
  # Instance identity for sub-agent turns (lineage id + invocation id);
205
207
  # both None on main-agent turns.
206
208
  self.sub_conversation_id = sub_conversation_id
@@ -23,6 +23,14 @@ class LLMStrategy(Protocol):
23
23
  """Generate a response from the LLM based on the agent's history."""
24
24
  ...
25
25
 
26
+ def validate_reasoning_effort_override(self, value: str) -> None:
27
+ """Raise ValueError unless the strategy can honor ``value`` at runtime."""
28
+ ...
29
+
30
+ def applied_reasoning_effort(self, context: "AgentContext") -> Optional[str]:
31
+ """Provider-native effort this call applies; None = no runtime effort."""
32
+ ...
33
+
26
34
  def prepare_initial_history(self, context: "AgentContext") -> List["Message"]:
27
35
  """Creates the initial message history for the planning phase."""
28
36
  ...
@@ -7,9 +7,9 @@ markdown checklist, which is written to the ``task.md`` **artifact**
7
7
  touches no closed-vocabulary kind; the checklist lives in the workspace manifest
8
8
  and persists in the snapshot.
9
9
 
10
- Uses the planning-state model (no ``state_model_overrides`` entry). It is
11
- sandbox-free — writing an artifact mutates ``context.artifacts`` (a framework
12
- field), not the sandbox filesystem.
10
+ Runs on the base strategy unless a ``state_llm_overrides`` entry routes it
11
+ elsewhere. It is sandbox-free — writing an artifact mutates
12
+ ``context.artifacts`` (a framework field), not the sandbox filesystem.
13
13
  """
14
14
 
15
15
  from typing import List, Optional, TYPE_CHECKING
@@ -221,6 +221,10 @@ class BaseLLMStrategy:
221
221
  - create_tool_response_message() for provider-specific formatting
222
222
  - parse_assistant_response() if message structure differs significantly
223
223
  - _extract_tool_call_args() for provider-specific argument extraction
224
+
225
+ Construction contract: creating a strategy performs no network I/O and
226
+ needs no live credentials, so configuration validators (e.g.
227
+ ``validate_state_llm_overrides``) stay runnable in CI.
224
228
  """
225
229
 
226
230
  #: Whether this strategy renders image parts (from the ``multimodal_parts``
@@ -297,6 +301,29 @@ class BaseLLMStrategy:
297
301
  clone.model = model
298
302
  return clone
299
303
 
304
+ def validate_reasoning_effort_override(self, value: str) -> None:
305
+ """Raise ValueError unless this strategy can honor ``value`` at runtime.
306
+
307
+ Called on the RESOLVED per-node strategy during
308
+ ``validate_state_llm_overrides`` — before any paid call. The base
309
+ default rejects, so a strategy that ignores the runtime effort hint
310
+ can never validate an override it would silently drop.
311
+ """
312
+ raise ValueError(
313
+ f"{type(self).__name__} does not support runtime "
314
+ f"reasoning-effort overrides (got {value!r})"
315
+ )
316
+
317
+ def applied_reasoning_effort(self, context: "AgentContext") -> Optional[str]:
318
+ """Provider-native description of the effort this call will apply.
319
+
320
+ ``None`` means the strategy applies no runtime effort — telemetry
321
+ must then record nothing rather than echo the state hint. The
322
+ vocabulary is provider-native by design; consumers must not assume
323
+ the OpenAI scale.
324
+ """
325
+ return None
326
+
300
327
  # ------------------------------------------------------------------
301
328
  # Response generation (template method)
302
329
  # ------------------------------------------------------------------
@@ -311,6 +338,12 @@ class BaseLLMStrategy:
311
338
  only the provider call in :meth:`_generate`; a strategy that merely
312
339
  implements ``_generate`` is fully traced.
313
340
  """
341
+ # Captured at call time: by the time the driver builds the turn
342
+ # event, the graph has already transitioned and the hints belong to
343
+ # the next node.
344
+ context.set_last_applied_reasoning_effort(
345
+ self.applied_reasoning_effort(context)
346
+ )
314
347
  # Identity attributes make a span self-describing in a mixed trace
315
348
  # (main agent + sub-agents share one trace): who asked, from which
316
349
  # graph node, on which turn.
@@ -386,13 +419,18 @@ class BaseLLMStrategy:
386
419
  def _telemetry_invocation_parameters(
387
420
  self, context: "AgentContext"
388
421
  ) -> Dict[str, Any]:
389
- """State hints recorded as ``llm.invocation_parameters`` on the span."""
390
- return {
422
+ """Call parameters recorded as ``llm.invocation_parameters`` on the span.
423
+
424
+ The effort entry is the APPLIED value, not the state hint — a
425
+ strategy that ignores the hint records no effort at all.
426
+ """
427
+ params: Dict[str, Any] = {
391
428
  "tool_choice": context.get_state_hint("tool_choice", "auto"),
392
- "reasoning_effort": context.get_state_hint(
393
- "reasoning_effort", "medium"
394
- ),
395
429
  }
430
+ effort = self.applied_reasoning_effort(context)
431
+ if effort is not None:
432
+ params["reasoning_effort"] = effort
433
+ return params
396
434
 
397
435
  def _telemetry_tools(
398
436
  self, context: "AgentContext"
@@ -462,7 +500,7 @@ class BaseLLMStrategy:
462
500
  prompt_template = (
463
501
  sub_agent_templates.get(f"task.{agent_name}")
464
502
  or sub_agent_templates.get("task")
465
- or "Create a plan to accomplish this task: {task}"
503
+ or "Create a plan to accomplish this task: {{ task }}"
466
504
  )
467
505
  rendered = render_template(
468
506
  prompt_template,
@@ -125,7 +125,7 @@ class HarmonyLLMStrategy(BaseLLMStrategy):
125
125
  def prepare_initial_history(self, context: "AgentContext") -> List["Message"]:
126
126
  """Creates the initial message history for the planning phase."""
127
127
  initial_plan_template = self.templates.get("planning", {}).get(
128
- "initial_plan", "Create a plan to accomplish this task: {task}"
128
+ "initial_plan", "Create a plan to accomplish this task: {{ task }}"
129
129
  )
130
130
 
131
131
  initial_plan = self._render_template(
@@ -234,7 +234,7 @@ class HarmonyLLMStrategy(BaseLLMStrategy):
234
234
  ) -> List["Message"]:
235
235
  """Build planning history with expert context from clarification."""
236
236
  initial_plan_template = self.templates.get("planning", {}).get(
237
- "initial_plan", "Create a plan to accomplish this task: {task}"
237
+ "initial_plan", "Create a plan to accomplish this task: {{ task }}"
238
238
  )
239
239
  initial_plan = self._render_template(
240
240
  initial_plan_template,
@@ -308,7 +308,7 @@ class HarmonyLLMStrategy(BaseLLMStrategy):
308
308
  """Task-decomposition input for harmony: task + plan → a checklist."""
309
309
  template = self.templates.get("task", {}).get(
310
310
  "decompose",
311
- "Break the plan into a concrete, checkable task list for the task: {task}",
311
+ "Break the plan into a concrete, checkable task list for the task: {{ task }}",
312
312
  )
313
313
  instructions = self._render_template(
314
314
  template, self._get_template_variables(context)
@@ -413,7 +413,7 @@ class HarmonyLLMStrategy(BaseLLMStrategy):
413
413
  ) -> List["Message"]:
414
414
  """Build planning history from an already-enriched task brief (Harmony)."""
415
415
  initial_plan_template = self.templates.get("planning", {}).get(
416
- "initial_plan", "Create a plan to accomplish this task: {task}"
416
+ "initial_plan", "Create a plan to accomplish this task: {{ task }}"
417
417
  )
418
418
  initial_plan = self._render_template(
419
419
  initial_plan_template,
@@ -71,6 +71,21 @@ def _coerce_to_dict(value: Any) -> Dict[str, Any]:
71
71
  return {"type": "object", "properties": {}}
72
72
 
73
73
 
74
+ def _reasoning_by_name(model: str) -> bool:
75
+ """Name-prefix default for reasoning capability (first-party families).
76
+
77
+ Heterogeneous OpenAI-compatible catalogs (e.g. Azure AI Foundry hosting
78
+ DeepSeek/Qwen/Kimi models) defeat any name heuristic — callers declare
79
+ the capability via the ``reasoning_model`` constructor argument instead.
80
+ """
81
+ return (
82
+ model.startswith("o1-")
83
+ or model.startswith("o3-")
84
+ or model.startswith("o4-")
85
+ or model.startswith("gpt-5")
86
+ )
87
+
88
+
74
89
  class OpenAILLMStrategy(BaseLLMStrategy):
75
90
  """Provider strategy that bridges OpenAI's Responses API with internal message models."""
76
91
 
@@ -100,6 +115,7 @@ class OpenAILLMStrategy(BaseLLMStrategy):
100
115
  azure_api_version: Optional[str] = None,
101
116
  request_timeout: float = 300.0,
102
117
  request_max_retries: int = 2,
118
+ reasoning_model: Optional[bool] = None,
103
119
  ) -> None:
104
120
  super().__init__(
105
121
  templates=templates,
@@ -140,12 +156,14 @@ class OpenAILLMStrategy(BaseLLMStrategy):
140
156
  self.azure_api_version = azure_api_version
141
157
  self.is_o1_model = self.model.startswith("o1-")
142
158
 
143
- # Reasoning models support the reasoning.effort parameter
159
+ # Reasoning models support the reasoning.effort parameter. An
160
+ # explicit declaration wins over the name heuristic and survives
161
+ # with_model cloning.
162
+ self._reasoning_declared = reasoning_model
144
163
  self.is_reasoning_model = (
145
- self.model.startswith("o1-")
146
- or self.model.startswith("o3-")
147
- or self.model.startswith("o4-")
148
- or self.model.startswith("gpt-5")
164
+ reasoning_model
165
+ if reasoning_model is not None
166
+ else _reasoning_by_name(self.model)
149
167
  )
150
168
 
151
169
  # ------------------------------------------------------------------
@@ -160,10 +178,9 @@ class OpenAILLMStrategy(BaseLLMStrategy):
160
178
  clone.model = model
161
179
  clone.is_o1_model = model.startswith("o1-")
162
180
  clone.is_reasoning_model = (
163
- model.startswith("o1-")
164
- or model.startswith("o3-")
165
- or model.startswith("o4-")
166
- or model.startswith("gpt-5")
181
+ self._reasoning_declared
182
+ if self._reasoning_declared is not None
183
+ else _reasoning_by_name(model)
167
184
  )
168
185
  if api_version is not None:
169
186
  clone.azure_api_version = api_version
@@ -185,6 +202,28 @@ class OpenAILLMStrategy(BaseLLMStrategy):
185
202
  )
186
203
  return clone
187
204
 
205
+ #: Provider's documented effort vocabulary — owned here, never in core.
206
+ _EFFORT_VALUES = frozenset({"none", "low", "medium", "high", "xhigh", "max"})
207
+
208
+ def validate_reasoning_effort_override(self, value: str) -> None:
209
+ if not self.is_reasoning_model:
210
+ raise ValueError(
211
+ f"model {self.model!r} is not reasoning-capable; a "
212
+ "reasoning-effort override would be silently dropped "
213
+ "(declare reasoning_model=True if the catalog model does "
214
+ "reason)"
215
+ )
216
+ if value not in self._EFFORT_VALUES:
217
+ raise ValueError(
218
+ f"unsupported reasoning effort {value!r} "
219
+ f"(known: {sorted(self._EFFORT_VALUES)})"
220
+ )
221
+
222
+ def applied_reasoning_effort(self, context: "AgentContext") -> Optional[str]:
223
+ if not self.is_reasoning_model:
224
+ return None
225
+ return context.get_state_hint("reasoning_effort", "medium")
226
+
188
227
  async def _generate(self, context: "AgentContext") -> LLMResponse:
189
228
  history = context.get_conversation_messages()
190
229
  serialized_history = self._serialize_history(history)
@@ -295,7 +334,7 @@ class OpenAILLMStrategy(BaseLLMStrategy):
295
334
  def prepare_initial_history(self, context: "AgentContext") -> List[Message]:
296
335
  template = (
297
336
  self.templates.get("planning", {}).get("initial_plan")
298
- or "Create a plan to accomplish this task: {task}"
337
+ or "Create a plan to accomplish this task: {{ task }}"
299
338
  )
300
339
  initial_plan = self._render_template(
301
340
  template,
@@ -354,7 +393,7 @@ class OpenAILLMStrategy(BaseLLMStrategy):
354
393
  """
355
394
  template = (
356
395
  self.templates.get("planning", {}).get("initial_plan")
357
- or "Create a plan to accomplish this task: {task}"
396
+ or "Create a plan to accomplish this task: {{ task }}"
358
397
  )
359
398
  system_prompt = self._render_template(
360
399
  template,
@@ -429,7 +468,7 @@ class OpenAILLMStrategy(BaseLLMStrategy):
429
468
  """
430
469
  template = (
431
470
  self.templates.get("task", {}).get("decompose")
432
- or "Break the plan into a concrete, checkable task list for the task: {task}"
471
+ or "Break the plan into a concrete, checkable task list for the task: {{ task }}"
433
472
  )
434
473
  system_prompt = self._render_template(
435
474
  template,
@@ -530,7 +569,7 @@ class OpenAILLMStrategy(BaseLLMStrategy):
530
569
  """
531
570
  template = (
532
571
  self.templates.get("planning", {}).get("initial_plan")
533
- or "Create a plan to accomplish this task: {task}"
572
+ or "Create a plan to accomplish this task: {{ task }}"
534
573
  )
535
574
  system_prompt = self._render_template(
536
575
  template,