dialectical-framework 2.0.8__tar.gz → 2.0.11__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 (232) hide show
  1. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/PKG-INFO +1 -1
  2. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/pyproject.toml +1 -1
  3. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/consultant/consultant.py +15 -2
  4. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/conversation_facilitator.py +12 -1
  5. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/transformation_sketch.py +39 -5
  6. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/settings.py +26 -2
  7. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/settings_context.py +2 -2
  8. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/bedrock_provider.py +56 -22
  9. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/format_compat.py +11 -0
  10. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/use_brain.py +98 -6
  11. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/LICENSE +0 -0
  12. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/PATENTS +0 -0
  13. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/README.md +0 -0
  14. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/__init__.py +0 -0
  15. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/__init__.py +0 -0
  16. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  17. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  18. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
  19. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/migration.py +0 -0
  20. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
  21. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  22. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  23. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  24. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  25. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  26. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  27. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
  28. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
  29. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
  30. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  31. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/agent_context.py +0 -0
  32. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  33. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  34. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  35. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  36. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  37. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  38. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  39. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  40. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
  41. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  42. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
  43. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  44. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  45. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/app_spec.py +0 -0
  46. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/apps.py +0 -0
  47. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
  48. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/execution_report.py +0 -0
  49. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  50. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  51. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  52. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  53. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  54. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  55. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  56. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  57. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  58. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  59. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  60. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  61. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  62. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  63. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  64. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
  65. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  66. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  67. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  68. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  69. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  70. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  71. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  72. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  73. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  74. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  75. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/stream_events.py +0 -0
  76. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/toolsets.py +0 -0
  77. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/agents/turn_timing.py +0 -0
  78. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/__init__.py +0 -0
  79. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  80. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  81. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/add_input.py +0 -0
  82. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  83. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  84. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  85. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  86. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  87. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  88. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  89. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  90. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/build_status.py +0 -0
  91. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  92. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  93. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  94. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  95. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  96. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  97. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  98. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  99. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  100. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  101. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  102. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  103. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  104. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  105. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
  106. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  107. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  108. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/discard.py +0 -0
  109. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  110. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  111. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  112. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  113. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  114. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/record_decision.py +0 -0
  115. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  116. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/source_digest.py +0 -0
  117. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  118. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  119. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  120. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  121. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  122. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/tetrad_candidates.py +0 -0
  123. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
  124. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/tetrad_sketch.py +0 -0
  125. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  126. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  127. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  128. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/concerns/view_sketch.py +0 -0
  129. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  130. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/enums/__init__.py +0 -0
  131. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/enums/causality_preset.py +0 -0
  132. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/enums/di.py +0 -0
  133. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/events/__init__.py +0 -0
  134. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/events/graph_event.py +0 -0
  135. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  136. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/events/progress_event.py +0 -0
  137. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/exceptions/__init__.py +0 -0
  138. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  139. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/exceptions/provider_errors.py +0 -0
  140. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  141. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/__init__.py +0 -0
  142. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  143. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  144. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  145. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  146. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  147. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  148. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  149. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  150. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  151. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  152. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/case.py +0 -0
  153. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  154. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/decision.py +0 -0
  155. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  156. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  157. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/input.py +0 -0
  158. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  159. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/note.py +0 -0
  160. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  161. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  162. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  163. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  164. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  165. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  166. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  167. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  168. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  169. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  170. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  171. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  172. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  173. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  174. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  175. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  176. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  177. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  178. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  179. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  180. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  181. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  182. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  183. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  184. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  185. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  186. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  187. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  188. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  189. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  190. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  191. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  192. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  193. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/rendering.py +0 -0
  194. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  195. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  196. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  197. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  198. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  199. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  200. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  201. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
  202. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  203. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  204. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  205. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  206. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  207. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  208. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/scope_context.py +0 -0
  209. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  210. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/views.py +0 -0
  211. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  212. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  213. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/protocols/__init__.py +0 -0
  214. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/protocols/has_config.py +0 -0
  215. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  216. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/py.typed +0 -0
  217. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/__init__.py +0 -0
  218. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/async_drain.py +0 -0
  219. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/call_census.py +0 -0
  220. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/chunking.py +0 -0
  221. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/concurrency.py +0 -0
  222. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/dc_replace.py +0 -0
  223. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  224. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/edge_context.py +0 -0
  225. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/effect_logger.py +0 -0
  226. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/input_context.py +0 -0
  227. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/order_transitions.py +0 -0
  228. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/progress.py +0 -0
  229. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/retry_accounting.py +0 -0
  230. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  231. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/thinking_compat.py +0 -0
  232. {dialectical_framework-2.0.8 → dialectical_framework-2.0.11}/src/dialectical_framework/utils/utterance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dialectical-framework
3
- Version: 2.0.8
3
+ Version: 2.0.11
4
4
  Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
5
5
  License: MIT
6
6
  Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "2.0.8"
3
+ version = "2.0.11"
4
4
  description = "A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.)."
5
5
  authors = ["Evaldas Taroza <evaldas@dialexity.com>"]
6
6
  readme = "README.md"
@@ -42,6 +42,7 @@ from contextlib import aclosing
42
42
  from typing import Any, AsyncGenerator, Optional
43
43
 
44
44
  from mirascope import llm
45
+ from mirascope.llm.exceptions import ProviderError
45
46
 
46
47
  from dialectical_framework.agents.advisor.advisor import ChatResponse
47
48
  from dialectical_framework.agents.advisor.system_prompts import (
@@ -145,9 +146,21 @@ _TOOL_TOKENS = (
145
146
 
146
147
  def _failed_draw(error: BaseException) -> FailedDrawView:
147
148
  """A failed draw's record: the exception's type, the provider's refusal
148
- category when it is a `ModelRefusal` that carries one, and its text."""
149
+ category when it is a `ModelRefusal` that carries one, and its text.
150
+
151
+ The type is the PROVIDER's where Mirascope wrapped one: its `ProviderError`
152
+ subclasses carry the SDK exception as `original_exception`, and that is
153
+ the name a host counts by (`APITimeoutError` for a stalled draw, where the
154
+ wrapper says only `TimeoutError` — the builtin's name too). `ParseError`
155
+ is not a `ProviderError` and keeps its own name.
156
+ """
157
+ original = (
158
+ getattr(error, "original_exception", None)
159
+ if isinstance(error, ProviderError)
160
+ else None
161
+ )
149
162
  return FailedDrawView(
150
- kind=type(error).__name__,
163
+ kind=type(original if isinstance(original, BaseException) else error).__name__,
151
164
  category=getattr(error, "category", None) if isinstance(error, ModelRefusal) else None,
152
165
  message=str(error) or None,
153
166
  )
@@ -567,8 +567,19 @@ class ConversationFacilitator(SettingsAware):
567
567
  self._strip_caller_from_messages(response.messages)
568
568
  # The continuation is a conversational round too (and it is
569
569
  # Mirascope's own request, so nothing in `use_brain` marks it).
570
+ # Retried like the streamed resume (`_start_stream_round`):
571
+ # since the Bedrock client's own retry went to 0
572
+ # (`bedrock_provider._SDK_MAX_RETRIES`) this is the one
573
+ # provider request with no other retry layer. Safe to re-ask
574
+ # — `resume_async` is `response.messages + [user(outputs)]`
575
+ # with no mutation of the response — and the tools have
576
+ # already run, so a retry re-sends their outputs, never
577
+ # re-executes them.
570
578
  with conversational_round():
571
- response = await response.resume(tool_outputs)
579
+ resuming = response
580
+ response = await retry_transient(
581
+ lambda: resuming.resume(tool_outputs), what="Resume"
582
+ )
572
583
  # Mirascope's own request, below `use_brain`: a provider that
573
584
  # does not raise on a refusal itself only says so here.
574
585
  if getattr(response, "finish_reason", None) == FinishReason.REFUSAL:
@@ -53,8 +53,30 @@ one-shot can replace it there is a separate, gated measurement
53
53
  (`one-shot-transformation.md`, "Open"). Texts only, nothing persisted, no
54
54
  scores — a number here would be an unchecked guess.
55
55
 
56
- Unthinking, like every concern: nothing measured thinking on this call, and
57
- the measured arm ran without it.
56
+ Unthinking, like every concern. Measured once, on Sonnet 5.5 under the
57
+ structured-output constraint (2026-10-09, `probe_structured_s_minus.py`, 100
58
+ pairs): the same call thinking at `low` (`format_mode="json", thinking="low"`)
59
+ kept Ac- (+4, within noise) where the in-turn wording alone cost it a little
60
+ (−8 / −4 over two runs), at no latency on that model (4.7 s against 4.4 s a
61
+ write); on S- it added nothing beyond the wording (+15 either way). Not wired:
62
+ on a model that keeps forced tool use it would also move this call to JSON
63
+ mode, where no figure for it was taken. The next lever, if Ac- under the
64
+ constraint needs one.
65
+
66
+ THE CONSTRAINT AND S- (2026-10-09)
67
+ =================================
68
+ On a model whose structured calls carry `output_config.format`
69
+ (`format_compat.structured_output_format`; Sonnet 5.5 first), the reply comes
70
+ back as one compact line with terser texts — no prose, no thinking, the same
71
+ request otherwise (`probe_structured_wire.py`) — and S- came back as the two
72
+ traps IN TURN: a distinct third failure in 42% of 100 (four runs: 42/44/41/42)
73
+ against 57% for the same call without the constraint (+15, CI +2..+28), which
74
+ is what the first app measured on its cards (63% → 41% / 33%). Ac-/Re- were
75
+ not hurt; the constraint helps them (96-98% valid against 91-93%). The rule
76
+ now names the in-turn forms (`SYNTHESIS_SHAPE`), which restores S- to the
77
+ unconstrained level under the constraint and is neutral without it. The
78
+ constraint stays: it ended the parse re-asks, and the loss was a shape the
79
+ rule did not name.
58
80
 
59
81
  SELECTION: AVAILABLE, OFF BY DEFAULT
60
82
  ====================================
@@ -99,8 +121,19 @@ TRANSFORMATION_POSITIONS = """1. The action (Ac): the concrete move that carries
99
121
 
100
122
  #: What the developments produce together. The S- sentences are the measured
101
123
  #: fix (`one-shot-transformation.md`: third-trap 4/17 → 12/17 on graph-built
102
- #: wheels once the synthesis prompt carried them) and are kept word for word.
103
- SYNTHESIS_SHAPE = """S+ is what emerges only when Ac+ and Re+ run together — a gain in dimension, not a middle: not "both", not the two strengths listed side by side. S- is the ONE named state Ac- and Re- produce together: a single way of failing that is neither trap on its own and not a choice between them. "Either X or Y", "X versus Y" and "X while Y" with X and Y the two traps are the inputs listed, not the collapse named."""
124
+ #: wheels once the synthesis prompt carried them) and are kept word for word,
125
+ #: with the two IN-TURN forms added on 2026-10-09: a writer under a structured-
126
+ #: output constraint (Sonnet 5.5, `format_compat.structured_output_format`)
127
+ #: wrote S- as the two traps in sequence ("X, then Y", "X alternating with Y")
128
+ #: in 13-14 of ~58 failures per 100 against 4 of 43 without the constraint,
129
+ #: and the rule named only the side-by-side forms. With them named: S- as a
130
+ #: distinct third failure 42% → 57% on 100 pairs, twice (+15 / +16, both clear
131
+ #: of noise), back to the unconstrained writer's 57%; neutral on Sonnet 5
132
+ #: (−1, 100 pairs). Price: a pairwise "which S- is more specific" judge
133
+ #: prefers the trap-restating headline (30-48 against this wording; an arm
134
+ #: that asked for particulars won that read 60-19 and lost the whole shape
135
+ #: gain), so that read is not a shape read. `tests/e2e/probe_structured_s_minus.py`.
136
+ SYNTHESIS_SHAPE = """S+ is what emerges only when Ac+ and Re+ run together — a gain in dimension, not a middle: not "both", not the two strengths listed side by side. S- is the ONE named state Ac- and Re- produce together: a single way of failing that is neither trap on its own and not a choice between them. "Either X or Y", "X versus Y", "X while Y", "X, then Y" and "X alternating with Y" with X and Y the two traps are the inputs listed, side by side or in turn, not the collapse named."""
104
137
 
105
138
  #: How a one-shot call builds it: the poles FIRST, because a degraded action
106
139
  #: needs an action to degrade (the measured order), then the developments,
@@ -236,7 +269,8 @@ class TransformationSketchDto(BaseModel):
236
269
  s_minus: str = Field(
237
270
  description=(
238
271
  "S- — what Ac- and Re- running together collapse into: ONE named way of "
239
- "failing that is neither T- nor A- on its own. Never 'either X or Y'."
272
+ "failing that is neither T- nor A- on its own. Never 'either X or Y', "
273
+ "'X then Y' or 'X alternating with Y'."
240
274
  )
241
275
  )
242
276
 
@@ -40,8 +40,8 @@ class Settings(BaseModel):
40
40
 
41
41
  Per process (read once, from the base `DialecticalReasoning.setup()` installs;
42
42
  a value inside `using_settings` has no effect): `graph_db_*` (the connection is
43
- a Singleton), `llm_connect_timeout_s` (the provider is registered once),
44
- `effect_log_dir` (installed in `setup`). `DIALEXITY_MAX_CONCURRENT_LLM_CALLS`
43
+ a Singleton), `llm_connect_timeout_s` and `llm_read_timeout_s` (the provider
44
+ is registered once), `effect_log_dir` (installed in `setup`). `DIALEXITY_MAX_CONCURRENT_LLM_CALLS`
45
45
  is not a field at all: an env var behind one process-wide semaphore.
46
46
  """
47
47
 
@@ -276,6 +276,29 @@ class Settings(BaseModel):
276
276
  # slow generation is not the problem being solved here.
277
277
  llm_connect_timeout_s: float = Field(default=30.0, description="TCP/TLS connect timeout for LLM calls, in seconds. Raise on slow or high-latency links.")
278
278
 
279
+ # Read timeout for the Bedrock client, in seconds: how long one provider
280
+ # call may go without a byte arriving before it raises (`APITimeoutError`,
281
+ # which the ladder retries ONCE — `use_brain._STALL_RETRY_MAX`).
282
+ #
283
+ # The SDK's default is 600s, and the client used to keep it, so a stalled
284
+ # call held whatever was waiting on it for ten minutes with no error and no
285
+ # retry on record (a 610s turn, `prompt-vs-machinery`; the handoff that set
286
+ # this field: one of three gathered draws hung a 20-31s card for 300s,
287
+ # 2026-10-09, Sonnet 5.5). On a NON-streamed call the body arrives whole,
288
+ # so this is a ceiling on one generation; on a streamed round it is the gap
289
+ # between two chunks, and a thinking turn streams its deltas (the
290
+ # facilitator asks for `include_thoughts`), so the long silence is the
291
+ # non-streamed case only. 120s clears every unstalled generation in the
292
+ # archive (the longest measured conversational rounds sit under a minute on
293
+ # Sonnet 5 with thinking) while cutting a stall from 600s to two minutes. A
294
+ # host whose calls are short sets it lower: the first app's draws run ~15s,
295
+ # texts ~8s, structured calls ~3s on Sonnet 5.5, and it runs at 30s. Too
296
+ # low, and a legitimately long generation (a digest of a 40k-character
297
+ # part, an Opus round with a large dump) is cut and re-asked — the retry
298
+ # does not help a call that needed the time. Read once, with the connect
299
+ # timeout, when the provider registers.
300
+ llm_read_timeout_s: float = Field(default=120.0, description="Read timeout for LLM calls, in seconds: the longest one call may wait for the provider's next byte. Lower where every call is short; a stall then surfaces as a timeout instead of holding the caller.")
301
+
279
302
  @classmethod
280
303
  def from_partial(cls, partial_settings: Optional[Settings] = None) -> Self:
281
304
  """
@@ -353,5 +376,6 @@ class Settings(BaseModel):
353
376
  # something. `**` on an empty dict sends nothing.
354
377
  **_env_thinking_level(),
355
378
  llm_connect_timeout_s=float(os.getenv("DIALEXITY_LLM_CONNECT_TIMEOUT_S", 30.0)),
379
+ llm_read_timeout_s=float(os.getenv("DIALEXITY_LLM_READ_TIMEOUT_S", 120.0)),
356
380
  effect_log_dir=os.getenv("DIALEXITY_GRAPH_LOG_DIR"),
357
381
  )
@@ -31,8 +31,8 @@ TWO TIERS, as the code already treats them (see `Settings`):
31
31
  through `SettingsAware.settings` (word limits, thinking level, the Advisor's
32
32
  floors and caps, the audit switches);
33
33
  - read ONCE per process, from the base only: `graph_db_*` (the connection is a
34
- Singleton), `llm_connect_timeout_s` (the provider is registered once),
35
- `effect_log_dir` (installed in `setup`). A value for these inside
34
+ Singleton), `llm_connect_timeout_s` and `llm_read_timeout_s` (the provider is
35
+ registered once), `effect_log_dir` (installed in `setup`). A value for these inside
36
36
  `using_settings` has no effect, by construction, not by accident.
37
37
 
38
38
  `container.settings.override(...)` RAISES: it would replace this provider with one
@@ -36,27 +36,51 @@ if TYPE_CHECKING:
36
36
  from mirascope.llm.tools import AsyncToolkit, Toolkit
37
37
 
38
38
 
39
- def _connect_timeout(connect_timeout_s: float | None) -> httpx.Timeout:
40
- """SDK default timeouts with a widened CONNECT phase only.
41
-
42
- The SDK's 5s connect timeout assumes a warm datacenter link. A cold TLS
43
- handshake over a tethered/VPN/mobile connection measured 7.7s, so the first
44
- call on every fresh connection failed — and the parallel stages failed
45
- hardest, since each concurrent call opens its own cold connection while a
46
- sequential one reuses a handshaked socket. Read/write keep the SDK defaults:
47
- a slow-to-connect link is a different problem from a slow generation, and
48
- conflating them would mask real hangs.
39
+ def _client_timeout(
40
+ connect_timeout_s: float | None, read_timeout_s: float | None = None
41
+ ) -> httpx.Timeout:
42
+ """SDK default timeouts with the CONNECT and READ phases set by the framework.
43
+
44
+ Connect: the SDK's 5s assumes a warm datacenter link. A cold TLS handshake
45
+ over a tethered/VPN/mobile connection measured 7.7s, so the first call on
46
+ every fresh connection failed — and the parallel stages failed hardest,
47
+ since each concurrent call opens its own cold connection while a sequential
48
+ one reuses a handshaked socket.
49
+
50
+ Read: the SDK's 600s is the length of a stall nobody asked for. A provider
51
+ call that stops sending holds its caller for ten minutes with no error, and
52
+ where callers are gathered (the view turn's best-of-3) one stalled draw
53
+ holds the two that finished (a 300s card where the same case took 20-31s,
54
+ 2026-10-09). `Settings.llm_read_timeout_s` sets it; a timeout raises
55
+ `APITimeoutError`, which the ladder classifies as a stall and re-asks once.
56
+ Write and pool keep the SDK defaults: nothing has stalled on either.
49
57
  """
50
- seconds = connect_timeout_s if connect_timeout_s is not None else 30.0
58
+ connect = connect_timeout_s if connect_timeout_s is not None else 30.0
59
+ read = read_timeout_s if read_timeout_s is not None else 120.0
51
60
  default = DEFAULT_TIMEOUT
52
61
  return httpx.Timeout(
53
- connect=seconds,
54
- read=default.read,
62
+ connect=connect,
63
+ read=read,
55
64
  write=default.write,
56
65
  pool=default.pool,
57
66
  )
58
67
 
59
68
 
69
+ #: The Anthropic SDK's own retry is OFF: every retry the framework makes is one
70
+ #: it can see. The SDK defaults to two retries of a timeout, a connection error
71
+ #: or a 408/409/429/5xx with its own backoff, BELOW `use_brain`'s ladder and
72
+ #: `retry_transient` — so a stalled call cost three read timeouts before the
73
+ #: ladder saw one failure, and `retry_accounting`/`call_census` recorded none of
74
+ #: it (a 610s turn with `retry_count` 0, `prompt-vs-machinery`). With this at 0
75
+ #: the bound on one call is the ladder's: a stall is (`_STALL_RETRY_MAX`) × the
76
+ #: read timeout plus its flat delay, and the retry is on the account. Every
77
+ #: provider request the framework makes passes a retry layer of its own — the
78
+ #: ladder (`use_brain`), the streamed open and resume (`retry_transient` in
79
+ #: `_start_stream_round`) and, since this went to 0, the awaited resume too
80
+ #: (`ConversationFacilitator.submit`), which until then had only the SDK's.
81
+ _SDK_MAX_RETRIES = 0
82
+
83
+
60
84
  #: Start of the Advisor's mutable graph dump inside its system prompt.
61
85
  #:
62
86
  #: `_CONTEXT_SLOT` (`agents/advisor/system_prompts.py`) is the LAST section and the
@@ -361,11 +385,19 @@ class BedrockAnthropicProvider(AnthropicProvider):
361
385
  id = "bedrock"
362
386
  default_scope = "bedrock/"
363
387
 
364
- def __init__(self, *, connect_timeout_s: float | None = None, **kwargs) -> None: # noqa: ARG002
388
+ def __init__(
389
+ self,
390
+ *,
391
+ connect_timeout_s: float | None = None,
392
+ read_timeout_s: float | None = None,
393
+ **kwargs, # noqa: ARG002
394
+ ) -> None:
365
395
  # Skip super().__init__() — parent creates Anthropic/AsyncAnthropic clients we don't need
366
- timeout = _connect_timeout(connect_timeout_s)
367
- self.client = AnthropicBedrock(timeout=timeout)
368
- self.async_client = AsyncAnthropicBedrock(timeout=timeout)
396
+ timeout = _client_timeout(connect_timeout_s, read_timeout_s)
397
+ self.client = AnthropicBedrock(timeout=timeout, max_retries=_SDK_MAX_RETRIES)
398
+ self.async_client = AsyncAnthropicBedrock(
399
+ timeout=timeout, max_retries=_SDK_MAX_RETRIES
400
+ )
369
401
  self._beta_provider = None
370
402
 
371
403
  @staticmethod
@@ -594,17 +626,19 @@ def ensure_bedrock_provider(
594
626
  ) -> None:
595
627
  """Register the bedrock provider if not already registered. Idempotent.
596
628
 
597
- Registration happens on the first call, so `llm_connect_timeout_s` is read
598
- from DI here rather than in `__init__` (Mirascope constructs providers with
599
- no arguments). Consequence of the idempotence: changing the setting after
600
- the first LLM call of the process has no effect.
629
+ Registration happens on the first call, so `llm_connect_timeout_s` and
630
+ `llm_read_timeout_s` are read from DI here rather than in `__init__`
631
+ (Mirascope constructs providers with no arguments). Consequence of the
632
+ idempotence: changing either setting after the first LLM call of the
633
+ process has no effect.
601
634
  """
602
635
  global _registered
603
636
  if _registered:
604
637
  return
605
638
  llm.register_provider(
606
639
  BedrockAnthropicProvider(
607
- connect_timeout_s=getattr(settings, "llm_connect_timeout_s", None)
640
+ connect_timeout_s=getattr(settings, "llm_connect_timeout_s", None),
641
+ read_timeout_s=getattr(settings, "llm_read_timeout_s", None),
608
642
  ),
609
643
  scope="bedrock/",
610
644
  )
@@ -38,6 +38,17 @@ refuse forced tool use — the ones whose structured calls go in JSON mode in
38
38
  the first place, explicitly (`format_mode="json"`) or by substitution — and
39
39
  to no other: Sonnet 5 / Opus 5 took it too in the probe, but every judged
40
40
  figure there was taken without it, so widening it is a measurement.
41
+
42
+ **The constraint changes what is written, not only how it parses.** Under it
43
+ the reply is one compact line with terser texts (`tests/e2e/
44
+ probe_structured_wire.py`), and `TransformationSketch`'s S- came back as the
45
+ two traps in turn: a distinct third failure 42% against 57% unconstrained on
46
+ Sonnet 5.5 (100 pairs, +15 clear of noise; the first app saw 63% → 33-41%),
47
+ while Ac-/Re- validity rose. The repair was the prompt, not the constraint:
48
+ `SYNTHESIS_SHAPE` names the in-turn forms since 2026-10-09 and S- is back at
49
+ 57% with the constraint on (`tests/e2e/probe_structured_s_minus.py`, the gate
50
+ for this seam on the transformation layer). A judged figure taken on a
51
+ forced-tool model does not transfer to the constrained call by itself.
41
52
  """
42
53
 
43
54
  from __future__ import annotations
@@ -86,7 +86,8 @@ def use_brain(
86
86
  Retries on ParseError (validation failures) with a FLAT delay — waiting does
87
87
  not fix a wrong shape, see `_PARSE_RETRY_DELAY_S` — and on rate limits /
88
88
  throttling, transient connection failures and transient 5xx with exponential
89
- backoff (each with its own curve — see `_is_connection_error`).
89
+ backoff (each with its own curve — see `_is_connection_error`); a provider
90
+ STALL (read timeout) is re-asked once (`_STALL_RETRY_MAX`).
90
91
  Automatically traces all LLM calls via Langfuse when configured.
91
92
 
92
93
  When ``format`` is provided, returns the parsed model instance.
@@ -192,6 +193,7 @@ def use_brain(
192
193
  rate_delay = _RATE_LIMIT_BASE_S
193
194
  connect_delay = _CONNECT_RETRY_BASE_S
194
195
  connect_attempts = 0
196
+ stall_attempts = 0
195
197
  server_delay = _SERVER_RETRY_BASE_S
196
198
  server_attempts = 0
197
199
  last_error: Exception | None = None
@@ -325,6 +327,33 @@ def use_brain(
325
327
  )
326
328
  await asyncio.sleep(rate_delay)
327
329
  rate_delay = min(rate_delay * 2.0, _RATE_LIMIT_CAP_S)
330
+ elif _is_stall_error(e):
331
+ # Checked BEFORE the connection branch: a read timeout is
332
+ # a connection error by class, and it must not get that
333
+ # curve's three attempts. Every stalled attempt has
334
+ # already cost the whole read timeout, so the bound on
335
+ # one call is `_STALL_RETRY_MAX` × that timeout plus one
336
+ # flat delay — the figure `Settings.llm_read_timeout_s`
337
+ # promises.
338
+ stall_attempts += 1
339
+ if stall_attempts >= _STALL_RETRY_MAX:
340
+ raise
341
+ last_error = e
342
+ logging.getLogger(__name__).warning(
343
+ "Provider stalled on %s (attempt %d/%d, %.0fs waited), "
344
+ "re-asking in %.0fs: %s",
345
+ format_name or method.__qualname__,
346
+ stall_attempts, _STALL_RETRY_MAX,
347
+ time.monotonic() - attempt_started, _STALL_RETRY_BASE_S, e,
348
+ )
349
+ if attempt < attempts - 1:
350
+ slept_s += _STALL_RETRY_BASE_S
351
+ record_retry(
352
+ "stall",
353
+ sleep_s=_STALL_RETRY_BASE_S,
354
+ attempt_s=time.monotonic() - attempt_started,
355
+ )
356
+ await asyncio.sleep(_STALL_RETRY_BASE_S)
328
357
  elif _is_connection_error(e):
329
358
  # Bounded separately from `attempts`: a down endpoint must
330
359
  # surface as an error, not consume the whole retry budget.
@@ -783,8 +812,61 @@ _CONNECT_RETRY_MAX = 3
783
812
  _CONNECT_RETRY_BASE_S = 2.0
784
813
 
785
814
 
815
+ #: A STALL — the provider accepted the request and then stopped sending — is
816
+ #: retried once, not on the connection curve. The two look alike by class (both
817
+ #: arrive as the SDK's `APITimeoutError`, Mirascope's `TimeoutError`) and differ
818
+ #: in what an attempt costs: a connect failure fails in milliseconds, a stall
819
+ #: fails after the WHOLE read timeout (`Settings.llm_read_timeout_s`). Three
820
+ #: attempts of that is three read timeouts before the caller hears anything,
821
+ #: and where callers are gathered (the view turn's best-of-3) every finished
822
+ #: draw waits for it. One re-ask is worth it — a stall is usually one bad
823
+ #: backend instance and a fresh request lands elsewhere — and a second stall in
824
+ #: a row is the ceiling. The delay is flat: waiting does not un-stall a server.
825
+ #: Bound on one call, in full: `_STALL_RETRY_MAX` × read timeout + the delay.
826
+ _STALL_RETRY_MAX = 2
827
+ _STALL_RETRY_BASE_S = 2.0
828
+
829
+
830
+ def _exception_chain(e: BaseException) -> Iterator[BaseException]:
831
+ """The exception and what it wraps: Mirascope's `original_exception`, then
832
+ `__cause__`/`__context__`. The SDK raises `APITimeoutError from
833
+ httpx.ReadTimeout` and Mirascope raises its `TimeoutError from` that, so the
834
+ phase that timed out is two links down."""
835
+ seen: set[int] = set()
836
+ pending: list[BaseException | None] = [e]
837
+ while pending:
838
+ current = pending.pop(0)
839
+ if current is None or id(current) in seen or len(seen) >= 10:
840
+ continue
841
+ seen.add(id(current))
842
+ yield current
843
+ pending.append(getattr(current, "original_exception", None))
844
+ pending.append(current.__cause__)
845
+ pending.append(current.__context__)
846
+
847
+
848
+ def _is_stall_error(e: Exception) -> bool:
849
+ """Detect a READ timeout: the request was sent and the provider went quiet.
850
+
851
+ Decided by the httpx phase class in the chain. `ReadTimeout` is a stall;
852
+ `ConnectTimeout`/`WriteTimeout`/`PoolTimeout` are the link, left to
853
+ `_is_connection_error`. A timeout class with no phase in its chain (the
854
+ SDK's `APITimeoutError` or Mirascope's `TimeoutError` raised bare) is read
855
+ as a stall too — the shorter budget is the safe guess.
856
+ """
857
+ names = [type(x).__name__ for x in _exception_chain(e)]
858
+ if any(name.endswith("ReadTimeout") for name in names):
859
+ return True
860
+ if any(
861
+ name.endswith(("ConnectTimeout", "WriteTimeout", "PoolTimeout"))
862
+ for name in names
863
+ ):
864
+ return False
865
+ return any(name in ("APITimeoutError", "TimeoutError") for name in names)
866
+
867
+
786
868
  def _is_connection_error(e: Exception) -> bool:
787
- """Detect transient network failures worth retrying.
869
+ """Detect transient network failures worth retrying — a stall excluded.
788
870
 
789
871
  Real failure this catches: the framework's parallel stages
790
872
  (ExplorationPipeline, ExploreTransformations) open many connections at once,
@@ -795,13 +877,18 @@ def _is_connection_error(e: Exception) -> bool:
795
877
  Matched by class name, not by import: Mirascope re-raises provider
796
878
  exceptions as its own `mirascope.llm.exceptions.ConnectionError`/`TimeoutError`,
797
879
  and importing those shadows the builtins in this module.
880
+
881
+ Exclusive with `_is_stall_error`, by construction: a read timeout is a
882
+ timeout by class but gets the stall budget, not this curve's three.
798
883
  """
884
+ if _is_stall_error(e):
885
+ return False
799
886
  name = type(e).__name__
800
887
  if name in ("ConnectionError", "TimeoutError", "APIConnectionError", "APITimeoutError"):
801
888
  return True
802
889
  # httpx raises distinct classes per phase (ConnectTimeout, ReadTimeout,
803
890
  # ConnectError, RemoteProtocolError); the shared suffix is the reliable tell.
804
- return name.endswith(("ConnectError", "ConnectTimeout", "ReadTimeout"))
891
+ return name.endswith(("ConnectError", "ConnectTimeout"))
805
892
 
806
893
 
807
894
  #: Provider-side transient failures: the request was well-formed and the service
@@ -898,6 +985,8 @@ def _transient_kind(e: Exception) -> Optional[str]:
898
985
  return None
899
986
  if _is_rate_limit_error(e):
900
987
  return "rate_limit"
988
+ if _is_stall_error(e):
989
+ return "stall"
901
990
  if _is_connection_error(e):
902
991
  return "connection"
903
992
  if _is_transient_server_error(e):
@@ -930,9 +1019,10 @@ async def retry_transient(
930
1019
  deterministic answer.
931
1020
 
932
1021
  Bounded per kind, with no overall ceiling, so the worst case is not any single
933
- curve: a failure sequence that keeps CHANGING kind gets each budget in full, 7
934
- attempts and ~51s of sleep before it surfaces. Bounded is what matters here, but
935
- "30s" is the throttle-only figure and not the promise.
1022
+ curve: a failure sequence that keeps CHANGING kind gets each budget in full, 8
1023
+ attempts and ~53s of sleep before it surfaces (plus a read timeout for the one
1024
+ stalled attempt). Bounded is what matters here, but "30s" is the throttle-only
1025
+ figure and not the promise.
936
1026
 
937
1027
  `operation` must be safe to call again. That is a real precondition and not a
938
1028
  formality: the caller re-sends a whole prompt, so it must have no side effects
@@ -946,6 +1036,7 @@ async def retry_transient(
946
1036
  """
947
1037
  delay = {
948
1038
  "rate_limit": _RATE_LIMIT_BASE_S,
1039
+ "stall": _STALL_RETRY_BASE_S,
949
1040
  "connection": _CONNECT_RETRY_BASE_S,
950
1041
  "server": _SERVER_RETRY_BASE_S,
951
1042
  }
@@ -960,6 +1051,7 @@ async def retry_transient(
960
1051
  #: surface as an error instead of spending a throttle-sized budget on it.
961
1052
  budget = {
962
1053
  "rate_limit": _RATE_LIMIT_RETRY_MAX,
1054
+ "stall": _STALL_RETRY_MAX,
963
1055
  "connection": _CONNECT_RETRY_MAX,
964
1056
  "server": _SERVER_RETRY_MAX,
965
1057
  }