aisolate-agent 0.3.1__tar.gz → 0.3.2__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 (211) hide show
  1. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/PKG-INFO +2 -2
  2. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/README.md +1 -1
  3. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/pyproject.toml +1 -1
  4. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/agent.py +24 -1
  5. aisolate_agent-0.3.2/src/aisolate/agent/core/__init__.py +41 -0
  6. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/context.py +29 -4
  7. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/conversation_log.py +5 -0
  8. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/graph.py +28 -1
  9. aisolate_agent-0.3.2/src/aisolate/agent/core/initial_state.py +48 -0
  10. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/graphs.py +10 -3
  11. aisolate_agent-0.3.2/src/aisolate/agent/states/__init__.py +64 -0
  12. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/substrate.py +8 -8
  13. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_architecture_assumptions.py +37 -0
  14. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_graph.py +80 -0
  15. aisolate_agent-0.3.2/src/aisolate/agent/tests/test_core/test_initial_state.py +109 -0
  16. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_resume_semantics.py +131 -0
  17. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_substrate.py +38 -0
  18. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_workspace_tools.py +76 -5
  19. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/workspace.py +15 -0
  20. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/workspace_tools.py +80 -29
  21. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate_agent.egg-info/PKG-INFO +2 -2
  22. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate_agent.egg-info/SOURCES.txt +2 -0
  23. aisolate_agent-0.3.1/src/aisolate/agent/core/__init__.py +0 -18
  24. aisolate_agent-0.3.1/src/aisolate/agent/states/__init__.py +0 -25
  25. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/setup.cfg +0 -0
  26. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/__init__.py +0 -0
  27. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/__init__.py +0 -0
  28. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/config/__init__.py +0 -0
  29. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/config/agent_settings.py +0 -0
  30. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/config/prompts/__init__.py +0 -0
  31. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/artifacts.py +0 -0
  32. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/budget.py +0 -0
  33. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/events.py +0 -0
  34. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/exceptions.py +0 -0
  35. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/llm_response.py +0 -0
  36. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/policy.py +0 -0
  37. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/protocols.py +0 -0
  38. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/snapshot.py +0 -0
  39. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/spine_compaction.py +0 -0
  40. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/tests/test_context_snapshots.py +0 -0
  41. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/tests/test_snapshot.py +0 -0
  42. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/views.py +0 -0
  43. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/core/workspace.py +0 -0
  44. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/logging/__init__.py +0 -0
  45. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/logging/config.py +0 -0
  46. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/logging/factory.py +0 -0
  47. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/logging/unified_logger.py +0 -0
  48. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/main.py +0 -0
  49. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/memory/__init__.py +0 -0
  50. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/memory/models.py +0 -0
  51. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/base.py +0 -0
  52. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/clarification.py +0 -0
  53. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/compaction.py +0 -0
  54. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/critic.py +0 -0
  55. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/executing.py +0 -0
  56. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/expert_synthesis.py +0 -0
  57. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/finished.py +0 -0
  58. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/hitl_pause.py +0 -0
  59. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/outputs.py +0 -0
  60. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/planning.py +0 -0
  61. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/states/task.py +0 -0
  62. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/status.py +0 -0
  63. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/__init__.py +0 -0
  64. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/llm/__init__.py +0 -0
  65. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/llm/base.py +0 -0
  66. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/llm/harmony.py +0 -0
  67. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/llm/oai.py +0 -0
  68. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/llm/qwen.py +0 -0
  69. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/memory/__init__.py +0 -0
  70. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/memory/in_memory.py +0 -0
  71. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/memory/tests/test_in_memory.py +0 -0
  72. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/strategies/memory/vector.py +0 -0
  73. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/__init__.py +0 -0
  74. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/decorators.py +0 -0
  75. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/factory.py +0 -0
  76. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/interface.py +0 -0
  77. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/noop.py +0 -0
  78. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/openinference.py +0 -0
  79. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/opentelemetry_impl.py +0 -0
  80. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/telemetry/utils.py +0 -0
  81. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/__init__.py +0 -0
  82. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/conftest.py +0 -0
  83. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/fakes.py +0 -0
  84. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/__init__.py +0 -0
  85. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_artifact_rows.py +0 -0
  86. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_change_bridge_echo.py +0 -0
  87. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_conversation_log.py +0 -0
  88. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_delegated_lineage_restore.py +0 -0
  89. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_event_agent_name_stamping.py +0 -0
  90. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_execute_turn_review_pending.py +0 -0
  91. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_firewall_property.py +0 -0
  92. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_legacy_snapshot.py +0 -0
  93. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_live_capture.py +0 -0
  94. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_locale_prelude.py +0 -0
  95. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_policy.py +0 -0
  96. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_restore_review_posture.py +0 -0
  97. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_run_streaming_cancel.py +0 -0
  98. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_spine_compaction.py +0 -0
  99. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_state_transitions.py +0 -0
  100. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_status_summarizer.py +0 -0
  101. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_subagent_graph.py +0 -0
  102. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_token_accumulation.py +0 -0
  103. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_turn_started_events.py +0 -0
  104. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_turn_usage_isolation.py +0 -0
  105. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_uniform_state.py +0 -0
  106. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_views.py +0 -0
  107. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_workspace_bridge_attribution.py +0 -0
  108. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_workspace_capture.py +0 -0
  109. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_core/test_workspace_changed_event.py +0 -0
  110. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/__init__.py +0 -0
  111. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/conftest.py +0 -0
  112. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_delegation_file_paths.py +0 -0
  113. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_delegation_recovery.py +0 -0
  114. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_locale_helpers.py +0 -0
  115. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_size_compaction.py +0 -0
  116. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_stall_clone_96919cf1.py +0 -0
  117. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_truncation_wedge_5b90b101.py +0 -0
  118. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_e2e/test_workspace_lifecycle.py +0 -0
  119. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/__init__.py +0 -0
  120. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/helpers.py +0 -0
  121. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/test_flagship_loop.py +0 -0
  122. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/test_followup_roundtrips.py +0 -0
  123. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/test_full_workflow.py +0 -0
  124. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/test_restore_roundtrips.py +0 -0
  125. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_integration/test_stalled_and_acceptance.py +0 -0
  126. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/__init__.py +0 -0
  127. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_compact_for_llm.py +0 -0
  128. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_critic_enforcer.py +0 -0
  129. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_critic_review_cap.py +0 -0
  130. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_emission_map.py +0 -0
  131. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_executing_metadata.py +0 -0
  132. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_executing_prefix_stability.py +0 -0
  133. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_executing_review_routing.py +0 -0
  134. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_planning_artifact.py +0 -0
  135. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_planning_metadata.py +0 -0
  136. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_progress_ledger.py +0 -0
  137. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_selection.py +0 -0
  138. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_size_compaction_wiring.py +0 -0
  139. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_states/test_stall_recovery.py +0 -0
  140. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/__init__.py +0 -0
  141. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_identity_block.py +0 -0
  142. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_llm_generate_span.py +0 -0
  143. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_llm_providers.py +0 -0
  144. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_llm_watchdog.py +0 -0
  145. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_prepare_history_contract.py +0 -0
  146. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_qwen_retry.py +0 -0
  147. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_qwen_strategy.py +0 -0
  148. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_qwen_truncation.py +0 -0
  149. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_qwen_workspace_envelope.py +0 -0
  150. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_scripted_llm_contract.py +0 -0
  151. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_telemetry_attributes.py +0 -0
  152. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_strategies/test_turn_budget_block.py +0 -0
  153. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/__init__.py +0 -0
  154. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_bundle_and_restore.py +0 -0
  155. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_delegation_identity.py +0 -0
  156. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_edit_blocks.py +0 -0
  157. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_fake_executor.py +0 -0
  158. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_handover_sidecars.py +0 -0
  159. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_load_handover_resolution.py +0 -0
  160. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_output_refs.py +0 -0
  161. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_sandbox_import_surface.py +0 -0
  162. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_send_actions_error.py +0 -0
  163. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_shell_executing_loop.py +0 -0
  164. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_shell_tools.py +0 -0
  165. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_subagent_failure_contract.py +0 -0
  166. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_subagent_handover.py +0 -0
  167. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_three_tier_tools.py +0 -0
  168. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tests/test_tools/test_workspace_actions.py +0 -0
  169. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/__init__.py +0 -0
  170. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/__init__.py +0 -0
  171. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/base.py +0 -0
  172. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/final_answer.py +0 -0
  173. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/load_handover.py +0 -0
  174. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/visit_page_action.py +0 -0
  175. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/web_search_action.py +0 -0
  176. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/actions/wikipedia_search_action.py +0 -0
  177. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/base.py +0 -0
  178. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/execution.py +0 -0
  179. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/functions/__init__.py +0 -0
  180. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/functions/base.py +0 -0
  181. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/functions/hitl.py +0 -0
  182. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/models.py +0 -0
  183. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/__init__.py +0 -0
  184. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/base.py +0 -0
  185. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/edit_blocks.py +0 -0
  186. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/files_ops.py +0 -0
  187. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/python_executor.py +0 -0
  188. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/shell.py +0 -0
  189. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/subagent.py +0 -0
  190. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/platform/web_search.py +0 -0
  191. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/tools/routing.py +0 -0
  192. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/trace.py +0 -0
  193. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/agent/utils.py +0 -0
  194. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/__init__.py +0 -0
  195. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/codec.py +0 -0
  196. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/errors.py +0 -0
  197. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/models.py +0 -0
  198. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/output_window.py +0 -0
  199. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/__init__.py +0 -0
  200. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/fake_vm.py +0 -0
  201. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/fixtures.py +0 -0
  202. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/test_codec_channel.py +0 -0
  203. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/test_output_window.py +0 -0
  204. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/common/tests/utils.py +0 -0
  205. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/logging/__init__.py +0 -0
  206. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/logging/formatters.py +0 -0
  207. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate/logging/processors.py +0 -0
  208. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate_agent.egg-info/dependency_links.txt +0 -0
  209. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate_agent.egg-info/entry_points.txt +0 -0
  210. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/src/aisolate_agent.egg-info/requires.txt +0 -0
  211. {aisolate_agent-0.3.1 → aisolate_agent-0.3.2}/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.1
3
+ Version: 0.3.2
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.1-py3-none-any.whl
33
+ pip install path/to/aisolate_agent-0.3.2-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.1-py3-none-any.whl
8
+ pip install path/to/aisolate_agent-0.3.2-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.1"
7
+ version = "0.3.2"
8
8
  description = "AI Agent with intelligent planning and reasoning"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -16,6 +16,7 @@ from .core.graph import AgentGraph
16
16
  from .core.protocols import LLMStrategy, MemoryStrategy
17
17
  from .core.snapshot import AgentSnapshot
18
18
  from .core.budget import RunBudget
19
+ from .core.initial_state import InitialState
19
20
  from .core.events import (
20
21
  AgentStartedEvent,
21
22
  TurnStartedEvent,
@@ -316,6 +317,7 @@ class Agent:
316
317
  self, task: str, conversation_id: str,
317
318
  *, cancel_event: "Optional[asyncio.Event]" = None,
318
319
  budget: Optional[RunBudget] = None,
320
+ initial_state: "Optional[InitialState]" = None,
319
321
  ):
320
322
  """Run the agent with streaming updates.
321
323
 
@@ -336,6 +338,10 @@ class Agent:
336
338
  ``cancel_event``, with reason ``time_budget`` /
337
339
  ``token_budget``. Both signals live on the root context;
338
340
  sub loops observe them through ``suspension_due()``.
341
+ initial_state: Log events and workspace files to seed beside the
342
+ task. Applied at CYCLE start only — a fresh run or a follow-up
343
+ on a finished conversation — and ignored on a mid-run resume,
344
+ which already holds its state.
339
345
 
340
346
  Yields:
341
347
  Dict updates about agent state, tool calls, and progress
@@ -398,6 +404,7 @@ class Agent:
398
404
  # through suspension_due() → root_context().
399
405
  context.cancel_event = cancel_event
400
406
  context.budget = budget
407
+ context.initial_state = initial_state
401
408
 
402
409
  # RESTORATION LOGIC - Load and apply snapshot if it exists
403
410
  snapshot = await context.memory.load_latest_snapshot(conversation_id)
@@ -1392,6 +1399,13 @@ class Agent:
1392
1399
  "turn": context.turn,
1393
1400
  "suspended_at": datetime.now(timezone.utc).isoformat(),
1394
1401
  }
1402
+ if reason == "turn_budget":
1403
+ # The cap that bound the run, so a caller can raise it without
1404
+ # knowing how this agent was configured. Resuming a turn-budget
1405
+ # suspension with an unchanged cap re-suspends without executing a
1406
+ # turn (see the check in the run loop), which is indistinguishable
1407
+ # from a run that tried and got nowhere.
1408
+ context.suspension["max_turns"] = self.settings.max_turns
1395
1409
  if reason == "hitl" and context.pending_hitl_signal is not None:
1396
1410
  context.suspension["signal"] = dict(
1397
1411
  context.pending_hitl_signal.__dict__
@@ -1596,11 +1610,17 @@ class Agent:
1596
1610
  # Each state projects its own prompt envelope from the log.
1597
1611
  if not context.conversation.events():
1598
1612
  context.seed_task()
1613
+ context.apply_initial_state()
1599
1614
 
1600
1615
  # Enter the graph's entry node (only if not restored from snapshot)
1601
1616
  if context.execution.current_state is None:
1602
1617
  context.enter_node(self.graph.entry)
1603
1618
 
1619
+ # Where this run picked up the counter. A resumed run that suspends
1620
+ # again having executed nothing did not try and fail — its budget
1621
+ # was never raised.
1622
+ turn_at_entry = context.turn
1623
+
1604
1624
  yield AgentStartedEvent(
1605
1625
  task=context.task,
1606
1626
  turn=context.turn,
@@ -1641,9 +1661,12 @@ class Agent:
1641
1661
  ):
1642
1662
  suspend_reason = "turn_budget"
1643
1663
  bound_logger.warning(
1644
- "Turn budget exhausted — suspending (resumable)",
1664
+ "Turn budget exhausted — suspending (resumable). "
1665
+ "A resume must RAISE max_turns; an unchanged cap "
1666
+ "re-suspends here without executing a turn.",
1645
1667
  turn=context.turn,
1646
1668
  max_turns=self.settings.max_turns,
1669
+ executed_this_run=context.turn - turn_at_entry,
1647
1670
  )
1648
1671
  break
1649
1672
 
@@ -0,0 +1,41 @@
1
+ """Core agent module containing foundational components.
2
+
3
+ Alongside the strategy protocols and the run context, this exports the workflow
4
+ composition surface — ``AgentGraph``, its routing/validation types, and the
5
+ ``policy`` routers — so a consumer can compose its own control flow from the
6
+ same parts ``aisolate.agent.graphs`` uses.
7
+ """
8
+
9
+ from . import policy
10
+ from .protocols import LLMStrategy, MemoryStrategy, Tool
11
+ from .exceptions import AgentError, StateTransitionError, ToolExecutionError
12
+ from .context import AgentContext
13
+ from .graph import (
14
+ AgentGraph,
15
+ GraphValidationError,
16
+ Router,
17
+ RoutingDecision,
18
+ RoutingError,
19
+ )
20
+ from .initial_state import InitialState, WorkspaceSeed
21
+ from .llm_response import TokenUsage, LLMResponse
22
+
23
+ __all__ = [
24
+ "LLMStrategy",
25
+ "MemoryStrategy",
26
+ "Tool",
27
+ "AgentError",
28
+ "StateTransitionError",
29
+ "ToolExecutionError",
30
+ "AgentContext",
31
+ "AgentGraph",
32
+ "InitialState",
33
+ "WorkspaceSeed",
34
+ "GraphValidationError",
35
+ "Router",
36
+ "RoutingDecision",
37
+ "RoutingError",
38
+ "policy",
39
+ "TokenUsage",
40
+ "LLMResponse",
41
+ ]
@@ -42,6 +42,7 @@ if TYPE_CHECKING: # pragma: no cover
42
42
  from ..tools.models import FinalAnswerResult
43
43
  from .budget import RunBudget
44
44
  from .graph import AgentGraph, RoutingDecision
45
+ from .initial_state import InitialState
45
46
 
46
47
  logger = get_logger("aisolate.agent.core.context")
47
48
 
@@ -304,6 +305,10 @@ class AgentContext:
304
305
  self.current_node: Optional[str] = None
305
306
  self._node_instances: Dict[str, "State"] = {}
306
307
 
308
+ # Caller-supplied opening state, applied at cycle start only (see
309
+ # apply_initial_state).
310
+ self.initial_state: Optional["InitialState"] = None
311
+
307
312
  # Review posture. ``review_count`` is derived from the log (see the
308
313
  # property); only the budget is a plain field.
309
314
  self.max_reviews: int = 2
@@ -533,6 +538,24 @@ class AgentContext:
533
538
  )
534
539
  )
535
540
 
541
+ def apply_initial_state(self) -> None:
542
+ """Apply the caller's supplied opening state, if any.
543
+
544
+ Called beside ``seed_task`` — the two cycle starts, a fresh run and a
545
+ follow-up — and nowhere else: a mid-run resume already holds this state
546
+ and re-applying it would overwrite live work. Files OVERLAY the
547
+ manifest, so a follow-up's restored artifacts survive.
548
+ """
549
+ state = self.initial_state
550
+ if not state:
551
+ return
552
+ for event in state.events:
553
+ self.conversation.append(event)
554
+ for seed in state.files:
555
+ self.artifacts.put_text(
556
+ seed.path, seed.text, kind=seed.kind, mime=seed.mime,
557
+ )
558
+
536
559
  def active_view(self) -> List[Message]:
537
560
  """The current state's projected LLM input (its curated view of the log).
538
561
 
@@ -719,9 +742,10 @@ class AgentContext:
719
742
  message is appended to the log as a ``steering`` event — binding
720
743
  user input for the remaining work, NOT a new task;
721
744
  * a finished cycle → a follow-up: the new user message becomes a new
722
- ``task`` event and the run starts at planning on a FRESH turn
723
- budget — planning's view seeds it with the latest compaction (or
724
- the unsuperseded prior final);
745
+ ``task`` event and the run starts at the graph's ``cycle_entry``
746
+ (``planning`` for both shipped shapes) on a FRESH turn budget —
747
+ planning's view seeds it with the latest compaction (or the
748
+ unsuperseded prior final);
725
749
  * unfinished with NO posture → crash-degraded: the same mid-run
726
750
  resume at ``current_node``, plus a synthetic note that the run was
727
751
  interrupted uncleanly (kernel state may be lost).
@@ -877,7 +901,8 @@ class AgentContext:
877
901
  ),
878
902
  ))
879
903
  self.seed_task(self._initial_task)
880
- self.enter_node("planning")
904
+ self.apply_initial_state()
905
+ self.enter_node(self.graph.cycle_entry)
881
906
  return
882
907
 
883
908
  # Suspended (stop / time_budget / token_budget) or crash-degraded
@@ -134,6 +134,11 @@ META_KEYS = frozenset(
134
134
  "omitted_count",
135
135
  "artifact_key",
136
136
  "tool_call_id",
137
+ # Who authored the event, when that is not the state whose phase it
138
+ # carries: "supplied" marks one the caller seeded at cycle start
139
+ # (see core.initial_state), which reads and renders exactly like a
140
+ # generated one and must stay distinguishable in a trace.
141
+ "origin",
137
142
  }
138
143
  )
139
144
 
@@ -5,10 +5,17 @@ An ``AgentGraph`` is the whole control-flow story of an agent: named nodes
5
5
  ``(node, Output type)``, and — where a transition is a decision rather than a
6
6
  constant — a pure policy router that returns a ``RoutingDecision``.
7
7
 
8
+ Everything the framework needs to know about a workflow is DECLARED here, not
9
+ inferred elsewhere from node names: where a run starts (``entry``), where a new
10
+ cycle starts (``cycle_entry``), and whether the run maintains the workspace
11
+ checklist (``workspace_ops``). A consumer composing its own graph therefore gets
12
+ the same behaviour the shipped shapes get, without the framework having to
13
+ recognise it.
14
+
8
15
  The graph is validated at construction, so an incoherent workflow fails when
9
16
  it is composed, not turns later mid-run:
10
17
 
11
- * the entry node exists; every edge references known nodes;
18
+ * the entry and cycle-entry nodes exist; every edge references known nodes;
12
19
  * every output a node declares in ``produces`` has a route;
13
20
  * at least one node is terminal (the run can end);
14
21
  * the view lint: every kind a node declares in ``emits`` is viewed by some
@@ -80,6 +87,17 @@ class AgentGraph:
80
87
  entry: str
81
88
  nodes: Mapping[str, Callable[[], "State"]]
82
89
  edges: Mapping[Tuple[str, Type["Output"]], Union[str, Router]]
90
+ #: Where a NEW CYCLE starts — a follow-up request on a finished
91
+ #: conversation. Distinct from ``entry`` because intake nodes run once per
92
+ #: conversation, not once per cycle: the standard graph enters at
93
+ #: ``clarification`` and re-enters at ``planning``. Defaults to ``entry``.
94
+ cycle_entry: Optional[str] = None
95
+ #: Whether this workflow maintains the workspace checklist and working
96
+ #: memory (``task.md`` / ``memory.md``, via the ``mark_tasks`` /
97
+ #: ``write_memory`` ops). Defaults to ``"task" in nodes`` — the node that
98
+ #: generates a checklist implies one — so a graph that obtains its
99
+ #: checklist another way declares it instead.
100
+ workspace_ops: Optional[bool] = None
83
101
  # One prototype instance per node, built during validation. Used for
84
102
  # declaration lookups (produces/emits/view/terminal) — never for runs.
85
103
  _prototypes: Dict[str, "State"] = field(
@@ -89,6 +107,10 @@ class AgentGraph:
89
107
  def __post_init__(self) -> None:
90
108
  object.__setattr__(self, "nodes", dict(self.nodes))
91
109
  object.__setattr__(self, "edges", dict(self.edges))
110
+ if self.cycle_entry is None:
111
+ object.__setattr__(self, "cycle_entry", self.entry)
112
+ if self.workspace_ops is None:
113
+ object.__setattr__(self, "workspace_ops", "task" in self.nodes)
92
114
  self._validate()
93
115
 
94
116
  # ---- routing -------------------------------------------------------------
@@ -130,6 +152,11 @@ class AgentGraph:
130
152
  raise GraphValidationError(
131
153
  f"entry node {self.entry!r} is not in nodes {sorted(self.nodes)}"
132
154
  )
155
+ if self.cycle_entry not in self.nodes:
156
+ raise GraphValidationError(
157
+ f"cycle_entry node {self.cycle_entry!r} is not in nodes "
158
+ f"{sorted(self.nodes)}"
159
+ )
133
160
 
134
161
  for name, factory in self.nodes.items():
135
162
  try:
@@ -0,0 +1,48 @@
1
+ """State a caller supplies at cycle start, beside the task.
2
+
3
+ A run's opening state had exactly one producer: the memory strategy's latest
4
+ snapshot for the conversation. The task, which is also opening state, was
5
+ already an argument — this makes the rest one too, so a caller that knows the
6
+ plan (a pipeline whose stage prompt IS the plan), or that wants to fork a
7
+ conversation, replay from a known state, or construct a mid-cycle posture in a
8
+ test, can say so instead of writing a snapshot first.
9
+
10
+ Applied where a CYCLE starts — a fresh run, or a follow-up on a finished
11
+ conversation — and ignored on a mid-run resume, whose state is already live and
12
+ must not be overwritten. ``events`` append to the log in order; ``files``
13
+ overlay the workspace manifest, so a follow-up's restored artifacts survive and
14
+ named slots are replaced.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass, field
20
+ from typing import TYPE_CHECKING, Optional, Tuple
21
+
22
+ if TYPE_CHECKING:
23
+ from .conversation_log import Event
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class WorkspaceSeed:
28
+ """One workspace file to place before the first turn.
29
+
30
+ ``kind`` is the framework's internal role for its reserved slots (see
31
+ ``core.workspace.named_kind``); ``None`` for an ordinary file.
32
+ """
33
+
34
+ path: str
35
+ text: str
36
+ kind: Optional[str] = None
37
+ mime: str = "text/markdown"
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class InitialState:
42
+ """Log events and workspace files to seed at cycle start."""
43
+
44
+ events: Tuple["Event", ...] = field(default_factory=tuple)
45
+ files: Tuple[WorkspaceSeed, ...] = field(default_factory=tuple)
46
+
47
+ def __bool__(self) -> bool:
48
+ return bool(self.events or self.files)
@@ -13,7 +13,8 @@ judges the handover), no compaction (the bounded output IS the handover), and
13
13
  therefore no HITL at all.
14
14
 
15
15
  Composition facts, not flags: an agent reviews iff the graph has a ``critic``
16
- node; a graph without clarification starts at planning. The workflow is
16
+ node; a graph without clarification starts at planning. Both shapes declare
17
+ ``cycle_entry="planning"`` — a follow-up skips intake. The workflow is
17
18
  substrate-independent — the same shapes run on the jupyter and shell bindings
18
19
  (see ``aisolate.agent.substrate``).
19
20
 
@@ -108,7 +109,9 @@ def build_workflow(
108
109
  if role == "sub":
109
110
  # The bounded output IS the handover — no review, no compaction.
110
111
  edges[("executing", FinalDrafted)] = "finished"
111
- return AgentGraph(entry=entry, nodes=nodes, edges=edges)
112
+ return AgentGraph(
113
+ entry=entry, nodes=nodes, edges=edges, cycle_entry="planning",
114
+ )
112
115
 
113
116
  nodes["compaction"] = CompactionState
114
117
  edges[("executing", FinalDrafted)] = policy.route_after_final
@@ -129,7 +132,11 @@ def build_workflow(
129
132
  edges[("critic", VerdictProduced)] = policy.route_after_verdict
130
133
  edges[("critic", ReviewFailed)] = policy.route_after_review_failure
131
134
 
132
- return AgentGraph(entry=entry, nodes=nodes, edges=edges)
135
+ # A follow-up re-enters at planning, never at clarification: intake happens
136
+ # once per conversation, and the human is not asked to restate the brief.
137
+ return AgentGraph(
138
+ entry=entry, nodes=nodes, edges=edges, cycle_entry="planning",
139
+ )
133
140
 
134
141
 
135
142
  STANDARD_GRAPH = build_workflow()
@@ -0,0 +1,64 @@
1
+ """States module for agent lifecycle management.
2
+
3
+ This is the supported surface for composing a workflow: the node classes, plus
4
+ the ``Output`` types that key an ``AgentGraph``'s edges. Compose them with
5
+ ``aisolate.agent.core.AgentGraph`` and the routers in
6
+ ``aisolate.agent.core.policy``; ``aisolate.agent.graphs.build_workflow`` is one
7
+ convenience constructor over the same parts, not the only way in.
8
+ """
9
+
10
+ from .base import AbstractState, State, StateResult
11
+ from .clarification import ClarificationState
12
+ from .compaction import CompactionState
13
+ from .critic import CriticState
14
+ from .executing import ExecutingState
15
+ from .expert_synthesis import ExpertSynthesisState
16
+ from .finished import FinishedState
17
+ from .hitl_pause import HITLPauseState
18
+ from .planning import PlanningState
19
+ from .task import TaskState
20
+ from .outputs import (
21
+ BriefProduced,
22
+ CompactionFailed,
23
+ CompactionWritten,
24
+ FinalDrafted,
25
+ IntakeComplete,
26
+ NoAction,
27
+ Output,
28
+ PlanProduced,
29
+ QuestionAsked,
30
+ ReviewFailed,
31
+ Stalled,
32
+ TasksProduced,
33
+ ToolExecuted,
34
+ VerdictProduced,
35
+ )
36
+
37
+ __all__ = [
38
+ "AbstractState",
39
+ "State",
40
+ "StateResult",
41
+ "ClarificationState",
42
+ "CompactionState",
43
+ "CriticState",
44
+ "ExecutingState",
45
+ "ExpertSynthesisState",
46
+ "FinishedState",
47
+ "HITLPauseState",
48
+ "PlanningState",
49
+ "TaskState",
50
+ "Output",
51
+ "BriefProduced",
52
+ "CompactionFailed",
53
+ "CompactionWritten",
54
+ "FinalDrafted",
55
+ "IntakeComplete",
56
+ "NoAction",
57
+ "PlanProduced",
58
+ "QuestionAsked",
59
+ "ReviewFailed",
60
+ "Stalled",
61
+ "TasksProduced",
62
+ "ToolExecuted",
63
+ "VerdictProduced",
64
+ ]
@@ -148,7 +148,7 @@ class ExecutionSubstrate:
148
148
 
149
149
  A sub-agent runs the forced lean workflow, not the graph its Agent
150
150
  was constructed against — so ops gated on the graph (the workspace
151
- ops) are re-derived here against the sub graph. Returns
151
+ ops it declares) are re-derived here against the sub graph. Returns
152
152
  ``(extra_actions, extra_tools)``: kernel CodeActions on jupyter,
153
153
  host-side tools on shell.
154
154
  """
@@ -208,11 +208,11 @@ class JupyterSubstrate(ExecutionSubstrate):
208
208
 
209
209
  actions["load_handover_data"] = LoadHandoverAction()
210
210
 
211
- # The task workflow (planning → task → executing) exposes the
212
- # workspace actions that maintain task.md / memory.md in the worker
213
- # directory.
211
+ # A workflow that maintains task.md / memory.md gets the ops that
212
+ # maintain them (the graph declares this; it is not read off the node
213
+ # set, so a supplied checklist keeps its tools).
214
214
  graph = self.agent.graph
215
- if graph is not None and "task" in getattr(graph, "nodes", {}):
215
+ if graph is not None and getattr(graph, "workspace_ops", False):
216
216
  from .tools.actions.workspace import MarkTasksAction, WriteMemoryAction
217
217
 
218
218
  actions["mark_tasks"] = MarkTasksAction()
@@ -221,7 +221,7 @@ class JupyterSubstrate(ExecutionSubstrate):
221
221
  return actions
222
222
 
223
223
  def delegation_ops(self, sub_graph) -> "tuple[Dict[str, Any], List[Any]]":
224
- if sub_graph is None or "task" not in getattr(sub_graph, "nodes", {}):
224
+ if sub_graph is None or not getattr(sub_graph, "workspace_ops", False):
225
225
  return {}, []
226
226
  from .tools.actions.workspace import MarkTasksAction, WriteMemoryAction
227
227
 
@@ -352,7 +352,7 @@ class ShellSubstrate(ExecutionSubstrate):
352
352
  ShellFinalAnswerTool(),
353
353
  ]
354
354
  graph = self.agent.graph
355
- if graph is not None and "task" in getattr(graph, "nodes", {}):
355
+ if graph is not None and getattr(graph, "workspace_ops", False):
356
356
  tools.append(MarkTasksTool())
357
357
  tools.append(WriteMemoryTool())
358
358
  for tool in tools:
@@ -365,7 +365,7 @@ class ShellSubstrate(ExecutionSubstrate):
365
365
  return {}
366
366
 
367
367
  def delegation_ops(self, sub_graph) -> "tuple[Dict[str, Any], List[Any]]":
368
- if sub_graph is None or "task" not in getattr(sub_graph, "nodes", {}):
368
+ if sub_graph is None or not getattr(sub_graph, "workspace_ops", False):
369
369
  return {}, []
370
370
  from .tools.platform.workspace_tools import MarkTasksTool, WriteMemoryTool
371
371
 
@@ -20,6 +20,43 @@ def test_canonical_message_is_harmony_primitive():
20
20
  assert Message is openai_harmony.Message
21
21
 
22
22
 
23
+ # ── The composition surface is complete ──────────────────────────────────────────
24
+ def test_every_state_class_is_exported():
25
+ """A node a consumer cannot import from ``aisolate.agent.states`` is a node
26
+ it cannot compose a graph with. ``TaskState`` was missing for exactly as
27
+ long as nothing checked."""
28
+ import importlib
29
+ import inspect
30
+ import pkgutil
31
+
32
+ import aisolate.agent.states as states_pkg
33
+
34
+ discovered = set()
35
+ for mod in pkgutil.iter_modules(states_pkg.__path__):
36
+ module = importlib.import_module(f"{states_pkg.__name__}.{mod.name}")
37
+ for name, obj in vars(module).items():
38
+ if (
39
+ inspect.isclass(obj)
40
+ and issubclass(obj, State)
41
+ and obj.__module__ == module.__name__
42
+ and not name.startswith("_")
43
+ ):
44
+ discovered.add(name)
45
+
46
+ missing = discovered - set(states_pkg.__all__)
47
+ assert not missing, f"states/__init__.py does not export: {sorted(missing)}"
48
+
49
+
50
+ def test_graph_composition_parts_are_exported_from_core():
51
+ """``AgentGraph`` + the policy routers are the composition kit: a consumer
52
+ building its own workflow must not have to reach past the package."""
53
+ import aisolate.agent.core as core
54
+
55
+ for name in ("AgentGraph", "RoutingDecision", "policy"):
56
+ assert name in core.__all__
57
+ assert hasattr(core, name)
58
+
59
+
23
60
  # ── Memory is an append-only log ──────────────────────────────────────────────────
24
61
  def test_agent_context_has_no_destructive_history_replace():
25
62
  """``AgentContext`` exposes no whole-history replacement or direct
@@ -104,6 +104,12 @@ class TestValidator:
104
104
  with pytest.raises(GraphValidationError, match="entry"):
105
105
  minimal_graph(entry="nowhere")
106
106
 
107
+ def test_unknown_cycle_entry_raises(self):
108
+ # Validated exactly like `entry`: a follow-up must not discover
109
+ # mid-run that its re-entry node does not exist.
110
+ with pytest.raises(GraphValidationError, match="cycle_entry"):
111
+ minimal_graph(cycle_entry="nowhere")
112
+
107
113
  def test_edge_targeting_unknown_node_raises(self):
108
114
  with pytest.raises(GraphValidationError, match="unknown"):
109
115
  minimal_graph(
@@ -344,6 +350,80 @@ class TestInstantiation:
344
350
  )
345
351
 
346
352
 
353
+ # ── 4b. Declarations: cycle entry & workspace posture ────────────────────────
354
+
355
+
356
+ class TaskNamedStub(State):
357
+ """A node registered under the reserved ``task`` name."""
358
+
359
+ phase = "task"
360
+
361
+
362
+ class TestCycleEntry:
363
+ """Where a NEW cycle starts is declared, not inferred from a node name."""
364
+
365
+ def test_defaults_to_entry(self):
366
+ assert minimal_graph().cycle_entry == "start"
367
+
368
+ def test_explicit_value_is_kept(self):
369
+ assert minimal_graph(cycle_entry="end").cycle_entry == "end"
370
+
371
+ @pytest.mark.parametrize(
372
+ "kwargs",
373
+ [
374
+ {},
375
+ {"clarification": False, "review": False},
376
+ {"clarification": False, "review": False, "tasks": True},
377
+ {"role": "sub"},
378
+ ],
379
+ )
380
+ def test_shipped_shapes_re_enter_at_planning(self, kwargs):
381
+ # A follow-up skips intake: the standard graph enters at
382
+ # `clarification` but re-enters at `planning`. This was a literal in
383
+ # the restore path; every shipped shape must keep that behaviour.
384
+ graph = build_workflow(**kwargs)
385
+ assert graph.cycle_entry == "planning"
386
+
387
+
388
+ class TestWorkspaceOps:
389
+ """Whether the run maintains task.md / memory.md is declared, and its
390
+ default reproduces the ``"task" in nodes`` proxy it replaces."""
391
+
392
+ def test_defaults_true_when_a_task_node_is_present(self):
393
+ graph = minimal_graph(
394
+ nodes={"start": SilentStub, "task": TaskNamedStub, "end": TerminalStub},
395
+ )
396
+ assert graph.workspace_ops is True
397
+
398
+ def test_defaults_false_without_a_task_node(self):
399
+ assert minimal_graph().workspace_ops is False
400
+
401
+ def test_can_be_declared_without_a_task_node(self):
402
+ # The case the proxy could not express: a graph that is handed its
403
+ # checklist instead of generating one still needs the ops that work it.
404
+ assert minimal_graph(workspace_ops=True).workspace_ops is True
405
+
406
+ def test_can_be_declined_with_a_task_node(self):
407
+ graph = minimal_graph(
408
+ nodes={"start": SilentStub, "task": TaskNamedStub, "end": TerminalStub},
409
+ workspace_ops=False,
410
+ )
411
+ assert graph.workspace_ops is False
412
+
413
+ @pytest.mark.parametrize(
414
+ "kwargs",
415
+ [
416
+ {},
417
+ {"clarification": False, "review": False},
418
+ {"clarification": False, "review": False, "tasks": True},
419
+ {"role": "sub"},
420
+ ],
421
+ )
422
+ def test_shipped_shapes_match_the_replaced_proxy(self, kwargs):
423
+ graph = build_workflow(**kwargs)
424
+ assert graph.workspace_ops == ("task" in graph.nodes)
425
+
426
+
347
427
  # ── 5. Extension: a custom graph runs end-to-end on the unmodified driver ────
348
428
 
349
429