dialectical-framework 2.0.12__tar.gz → 2.1.1__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.12 → dialectical_framework-2.1.1}/PKG-INFO +1 -1
  2. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/pyproject.toml +1 -1
  3. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/consultant/consultant.py +18 -3
  4. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/tetrad_candidates.py +70 -12
  5. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/tetrad_sketch.py +10 -2
  6. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/view_sketch.py +38 -1
  7. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/settings.py +20 -2
  8. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/bedrock_provider.py +146 -0
  9. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/LICENSE +0 -0
  10. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/PATENTS +0 -0
  11. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/README.md +0 -0
  12. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/__init__.py +0 -0
  13. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/__init__.py +0 -0
  14. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  15. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  16. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
  17. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/migration.py +0 -0
  18. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
  19. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  20. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  21. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  22. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  23. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  24. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  25. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
  26. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
  27. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
  28. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  29. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/agent_context.py +0 -0
  30. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  31. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  32. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  33. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  34. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  35. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  36. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  37. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  38. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
  39. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  40. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
  41. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  42. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  43. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/app_spec.py +0 -0
  44. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/apps.py +0 -0
  45. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
  46. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  47. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/execution_report.py +0 -0
  48. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  49. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  50. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  51. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  52. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  53. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  54. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  55. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  56. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  57. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  58. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  59. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  60. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  61. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  62. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  63. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
  64. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  65. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  66. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  67. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  68. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  69. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  70. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  71. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  72. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  73. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  74. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/stream_events.py +0 -0
  75. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/toolsets.py +0 -0
  76. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/agents/turn_timing.py +0 -0
  77. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/__init__.py +0 -0
  78. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  79. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  80. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/add_input.py +0 -0
  81. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  82. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  83. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  84. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  85. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  86. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  87. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  88. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  89. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/build_status.py +0 -0
  90. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  91. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  92. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  93. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  94. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  95. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  96. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  97. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  98. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  99. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  100. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  101. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  102. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  103. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  104. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
  105. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  106. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  107. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/discard.py +0 -0
  108. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  109. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  110. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  111. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  112. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  113. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/record_decision.py +0 -0
  114. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  115. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/source_digest.py +0 -0
  116. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  117. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  118. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  119. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  120. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  121. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
  122. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  123. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  124. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  125. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/concerns/transformation_sketch.py +0 -0
  126. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  127. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/enums/__init__.py +0 -0
  128. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/enums/causality_preset.py +0 -0
  129. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/enums/di.py +0 -0
  130. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/events/__init__.py +0 -0
  131. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/events/graph_event.py +0 -0
  132. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  133. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/events/progress_event.py +0 -0
  134. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/exceptions/__init__.py +0 -0
  135. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  136. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/exceptions/provider_errors.py +0 -0
  137. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  138. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/__init__.py +0 -0
  139. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  140. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  141. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  142. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  143. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  144. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  145. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  146. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  147. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  148. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  149. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/case.py +0 -0
  150. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  151. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/decision.py +0 -0
  152. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  153. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  154. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/input.py +0 -0
  155. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  156. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/note.py +0 -0
  157. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  158. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  159. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  160. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  161. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  162. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  163. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  164. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  165. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  166. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  167. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  168. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  169. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  170. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  171. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  172. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  173. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  174. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  175. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  176. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  177. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  178. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  179. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  180. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  181. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  182. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  183. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  184. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  185. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  186. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  187. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  188. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  189. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  190. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/rendering.py +0 -0
  191. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  192. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  193. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  194. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  195. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  196. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  197. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  198. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
  199. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  200. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  201. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  202. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  203. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  204. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  205. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/scope_context.py +0 -0
  206. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  207. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/views.py +0 -0
  208. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  209. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  210. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/protocols/__init__.py +0 -0
  211. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/protocols/has_config.py +0 -0
  212. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  213. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/py.typed +0 -0
  214. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/settings_context.py +0 -0
  215. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/__init__.py +0 -0
  216. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/async_drain.py +0 -0
  217. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/call_census.py +0 -0
  218. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/chunking.py +0 -0
  219. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/concurrency.py +0 -0
  220. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/dc_replace.py +0 -0
  221. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  222. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/edge_context.py +0 -0
  223. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/effect_logger.py +0 -0
  224. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/format_compat.py +0 -0
  225. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/input_context.py +0 -0
  226. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/order_transitions.py +0 -0
  227. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/progress.py +0 -0
  228. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/retry_accounting.py +0 -0
  229. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  230. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/thinking_compat.py +0 -0
  231. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/src/dialectical_framework/utils/use_brain.py +0 -0
  232. {dialectical_framework-2.0.12 → dialectical_framework-2.1.1}/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.12
3
+ Version: 2.1.1
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.12"
3
+ version = "2.1.1"
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"
@@ -425,11 +425,13 @@ class Consultant:
425
425
  """
426
426
  from dialectical_framework.concerns.view_sketch import (
427
427
  ViewSketchDto, exploration_view_from_sketch, history_ask,
428
- history_text, perspective_from_sketch, view_sketch_prompt)
428
+ history_text, judge_context, perspective_from_sketch,
429
+ view_sketch_prompt)
429
430
 
430
431
  import asyncio
431
432
  import dataclasses
432
433
 
434
+ from dialectical_framework.agents.advisor.migration import person_turns
433
435
  from dialectical_framework.concerns.tetrad_candidates import (
434
436
  clamp_attempts, ranking, select_sketch)
435
437
 
@@ -459,7 +461,6 @@ class Consultant:
459
461
  sketch_turn._messages = list(history)
460
462
  return await sketch_turn.submit(ViewSketchDto, request)
461
463
 
462
- n = clamp_attempts(attempts)
463
464
  # Fully parallel, deliberately. On a COLD cache all N draws write the
464
465
  # ~12k system head instead of one writing and the rest reading (~$0.06
465
466
  # a card at list prices, 2026-10-06), and the fix would be to await the
@@ -470,6 +471,13 @@ class Consultant:
470
471
  # (`utils/bedrock_provider.py::_CACHE_TTL`) attacks the same cost
471
472
  # without the latency, which is the trade taken. Revisit only with a
472
473
  # measured cold-start rate.
474
+ n = clamp_attempts(attempts)
475
+ # The SAME request N times, by measurement: N identical requests draw
476
+ # the same opposition N times, reworded (the first app, 52/52 cards),
477
+ # and asking draw 2..N for a different one made the spares differ and
478
+ # the winner worse (CC 19/40 against 27/40, 2026-10-13,
479
+ # `tests/e2e/probe_view_turn_ask.py`) — so this is a coherence re-roll,
480
+ # and a host's "another" served from `runners_up` is a rewording.
473
481
  drawn = await asyncio.gather(*(draw() for _ in range(n)), return_exceptions=True)
474
482
  sketches = [d for d in drawn if not isinstance(d, BaseException)]
475
483
  failures = [d for d in drawn if isinstance(d, BaseException)]
@@ -503,13 +511,20 @@ class Consultant:
503
511
  # screen shows is that first tension. The judged draws that lost come
504
512
  # back too, best first, as `runners_up` — a host's "again"/"another"
505
513
  # can serve one without a new call (the module's stated intent).
514
+ # The judge reads the PERSON's turns as its context (`judge_context`):
515
+ # it never sees T or A, and called blind — as it was here until
516
+ # 2026-10-13, while the build and the persisted path passed their
517
+ # material — it scored the four aspects against nothing.
506
518
  runners_up: list[PerspectiveView] = []
507
519
  if len(sketches) > 1:
508
520
  firsts = [s.tensions[0] if s.tensions else None for s in sketches]
509
521
  judgeable = [i for i, t in enumerate(firsts) if t is not None and all(
510
522
  (getattr(t, k) or "").strip() for k in ("thesis", "antithesis", "t_plus", "t_minus", "a_plus", "a_minus"))]
511
523
  if len(judgeable) > 1:
512
- _best, verdicts = await select_sketch([firsts[i] for i in judgeable])
524
+ _best, verdicts = await select_sketch(
525
+ [firsts[i] for i in judgeable],
526
+ context=judge_context(person_turns(history)),
527
+ )
513
528
  order = ranking(verdicts)
514
529
  sketch = sketches[judgeable[order[0]]]
515
530
  runners_up = [perspective_from_sketch(firsts[judgeable[i]]) for i in order[1:]]
@@ -12,6 +12,22 @@ parallel, the two control statements judged for each, the best kept. The
12
12
  runners-up are returned, not thrown away — a person's "another" can be one of
13
13
  them without a new call.
14
14
 
15
+ The N draws are N identical requests, and they draw the SAME opposition N
16
+ times, reworded (the first app's handoff, 2026-10-13: 52/52 cards, the chosen
17
+ A at 0.79 similarity to its spares, a served runner-up "the same tension
18
+ re-worded" in 8/8 retries) — a coherence re-roll, which is what the figures
19
+ above measure, not a choice between tensions. Asking draw 2..N for a
20
+ DIFFERENT opposition was tried and rejected the same day on a pre-registered
21
+ A/B (`tests/e2e/probe_view_turn_ask.py`, pinned, 40 utterances): the spares
22
+ became different positions (75/80) and the winner lost — CC 19/40 against
23
+ 27/40, positions 34 against 37, 1.26x the seconds. Temperature is not a
24
+ lever either: both one-shot writers think, and a thinking call takes the
25
+ provider's default. The rejected ask and the record:
26
+ `docs/dev-notes/antithesis-selection.md`, "Best-of-3 drew the same tetrad
27
+ three times". What the handoff found beside it is kept: the ranking reads
28
+ dialectical validity after the pass floor (`ranking`), and the view turn's
29
+ judge reads the person's words (`view_sketch.judge_context`).
30
+
15
31
  What this must NOT become: a loop that optimises against the judge. The judge
16
32
  never sees the thesis or antithesis — it scores the four aspects' coherence
17
33
  and passes a MIRROR antithesis more readily than a genuine position — so a
@@ -75,17 +91,41 @@ def clamp_attempts(attempts: Optional[int], default: Optional[int] = None) -> in
75
91
 
76
92
  @dataclass(frozen=True)
77
93
  class SketchVerdict:
78
- """The two control statements' coherence scores for one sketch."""
94
+ """The two control statements' scores for one sketch: coherence (CC) on
95
+ both, and dialectical validity (DV) on both where the judge gave it.
96
+
97
+ DV is scored in the SAME call as CC (`CoherenceEvaluationDto`), so carrying
98
+ it costs nothing; until 2026-10-13 it was computed on every draft and
99
+ dropped here, and the ranking read coherence alone (the first app's
100
+ handoff). `None` on the DV fields means the judge did not say — a verdict
101
+ built from CC alone — and such a verdict ranks BELOW any with a DV, never
102
+ as a zero (a zero is a claim).
103
+ """
79
104
 
80
105
  t_plus_without_a_plus: float
81
106
  a_plus_without_t_plus: float
82
107
  reasoning: str = ""
108
+ t_plus_without_a_plus_dv: Optional[float] = None
109
+ a_plus_without_t_plus_dv: Optional[float] = None
83
110
 
84
111
  @property
85
112
  def floor(self) -> float:
86
113
  """The weaker statement — what the pass/fail rule reads."""
87
114
  return min(self.t_plus_without_a_plus, self.a_plus_without_t_plus)
88
115
 
116
+ @property
117
+ def mean(self) -> float:
118
+ return (self.t_plus_without_a_plus + self.a_plus_without_t_plus) / 2
119
+
120
+ @property
121
+ def dialectical_validity(self) -> Optional[float]:
122
+ """The mean DV of the two statements — whether the relationship is a
123
+ natural, generative one rather than a forced pairing. `None` when the
124
+ judge gave none."""
125
+ if self.t_plus_without_a_plus_dv is None or self.a_plus_without_t_plus_dv is None:
126
+ return None
127
+ return (self.t_plus_without_a_plus_dv + self.a_plus_without_t_plus_dv) / 2
128
+
89
129
  @property
90
130
  def passes(self) -> bool:
91
131
  return self.floor >= CONCEPTUAL_COHERENCE_THRESHOLD
@@ -153,6 +193,8 @@ async def judge_sketches(
153
193
  t_plus_without_a_plus=first.coherence_score,
154
194
  a_plus_without_t_plus=second.coherence_score,
155
195
  reasoning=f"{first.reasoning} / {second.reasoning}".strip(" /"),
196
+ t_plus_without_a_plus_dv=first.dialectical_validity,
197
+ a_plus_without_t_plus_dv=second.dialectical_validity,
156
198
  )
157
199
  except Exception as exc: # noqa: BLE001 — degrade to "unjudged"
158
200
  logger.warning("Judging a sketch failed; it sorts last: %s", exc)
@@ -167,10 +209,11 @@ async def select_sketch(
167
209
  """Judge every candidate (gathered) and return the index of the best plus
168
210
  all verdicts (None where judging failed).
169
211
 
170
- Ranking: the weaker control statement first (the pass rule), then the
171
- mean, then the earlier draw — a deterministic tie-break, not a preference.
172
- A candidate whose judging failed sorts last; if every judging failed the
173
- first draw is kept, which is exactly what `attempts=1` would have done.
212
+ Ranking (`ranking`): the weaker control statement first (the pass rule),
213
+ then dialectical validity, then the mean coherence, then the earlier draw
214
+ — a deterministic tie-break, not a preference. A candidate whose judging
215
+ failed sorts last; if every judging failed the first draw is kept, which
216
+ is exactly what `attempts=1` would have done.
174
217
 
175
218
  ONE candidate is never judged: there is nothing to select among, so
176
219
  `attempts=1` costs no extra call.
@@ -183,15 +226,30 @@ async def select_sketch(
183
226
 
184
227
 
185
228
  def ranking(verdicts: Sequence[Optional[SketchVerdict]]) -> list[int]:
186
- """Candidate indices best first, by `select_sketch`'s rule: the weaker
187
- control statement, then the mean, then the earlier draw; unjudged last.
188
- `[0]` is the winner; the rest are the runners-up in order."""
229
+ """Candidate indices best first: the weaker control statement, then
230
+ dialectical validity, then the mean coherence, then the earlier draw;
231
+ unjudged last. `[0]` is the winner; the rest are the runners-up in order.
232
+
233
+ Why the floor stays FIRST: it is the pass rule, and every best-of-N figure
234
+ on record (CC 36/40 on the view turn, 30/40 on the build, 26/46 on
235
+ documents) is a keep-any-passing-draw figure — a term ahead of it would
236
+ trade those for an unmeasured one. Why DV comes next, ahead of the mean:
237
+ DV is the paper's acceptance term beside SP (the Advisor's quality floor
238
+ already reads it), and it answers the question the choice is about — of
239
+ two draws that both hold, which is the LIVE tension rather than the
240
+ forced pairing — where the mean is only the other statement's score,
241
+ already above the floor. On the archive DV tracks CC at 0.96, so this
242
+ moves few selections — the draws of one request are rewordings of one
243
+ tension. A verdict with no DV sorts
244
+ below any with one (`-1.0` exists only to lose this term, like
245
+ `explorer._causality_probability`), never as a zero.
246
+ """
189
247
 
190
- def key(index: int) -> tuple[int, float, float, int]:
248
+ def key(index: int) -> tuple[int, float, float, float, int]:
191
249
  verdict = verdicts[index]
192
250
  if verdict is None:
193
- return (0, 0.0, 0.0, -index)
194
- mean = (verdict.t_plus_without_a_plus + verdict.a_plus_without_t_plus) / 2
195
- return (1, verdict.floor, mean, -index)
251
+ return (0, 0.0, 0.0, 0.0, -index)
252
+ dv = verdict.dialectical_validity
253
+ return (1, verdict.floor, -1.0 if dv is None else dv, verdict.mean, -index)
196
254
 
197
255
  return sorted(range(len(verdicts)), key=key, reverse=True)
@@ -128,8 +128,10 @@ class TetradSketch(ReasonableConcern[ViewSketchPerspectiveDto], SettingsAware):
128
128
  attempts: Optional[int] = None,
129
129
  ) -> ViewSketchPerspectiveDto:
130
130
  """One tension, whole. With `attempts` > 1 (`clamp_attempts`, max 3) the
131
- call is drawn that many times IN PARALLEL and the framework's own
132
- coherence check picks the best (`concerns/tetrad_candidates.py`); the
131
+ call is drawn that many times IN PARALLEL — the SAME request each
132
+ time: asking later draws for a different opposition was measured and
133
+ rejected on the view turn (`view_sketch_prompt`'s docstring) — and
134
+ the framework's own coherence check picks the best (`concerns/tetrad_candidates.py`); the
133
135
  runners-up stay on `self.alternatives` with their verdicts, the
134
136
  winner's verdict on `self.verdict`. Measured reason and the guard
135
137
  against turning this into a loop: that module's docstring.
@@ -171,6 +173,12 @@ class TetradSketch(ReasonableConcern[ViewSketchPerspectiveDto], SettingsAware):
171
173
  "drawn": len(candidates),
172
174
  "selected": best,
173
175
  "floors": [None if v is None else round(v.floor, 2) for v in verdicts],
176
+ "validity": [
177
+ None if v is None or v.dialectical_validity is None
178
+ else round(v.dialectical_validity, 2)
179
+ for v in verdicts
180
+ ],
181
+ "oppositions": [c.antithesis for c in candidates],
174
182
  "alternatives": [c.model_dump() for c, _v in self.alternatives],
175
183
  }
176
184
  return tension
@@ -45,7 +45,7 @@ The view BEFORE the upgrade, not a lighter version of the checked one.
45
45
 
46
46
  from __future__ import annotations
47
47
 
48
- from typing import Optional
48
+ from typing import Optional, Sequence
49
49
 
50
50
  from pydantic import BaseModel, Field
51
51
 
@@ -215,6 +215,33 @@ For each pair:
215
215
  THESIS_IS_FIXED = """The thesis (T) is GIVEN, in the person's own words, and it is FIXED: carry it into the view exactly as it stands — do not reword, shorten, sharpen or generalise it, and do not put a different position in its place. Everything else in the tetrad is built around it as given."""
216
216
 
217
217
 
218
+
219
+ #: How much of the person's own words the view turn's JUDGE reads (characters,
220
+ #: newest turns kept whole). The judge never sees T or A, so without context
221
+ #: it scores the four aspects' coherence against nothing: the persisted path
222
+ #: and the one-shot build pass their material, and the view turn passed ""
223
+ #: until 2026-10-13 (the first app's handoff, §3). The person's turns are what
224
+ #: the migration mines as material too (`migration.person_turns`); a card's
225
+ #: history is one utterance, a long consultation is bounded here so six judge
226
+ #: calls do not each re-read it whole (no cache breakpoint sits on a judge
227
+ #: call). Policy, not config.
228
+ JUDGE_CONTEXT_CHARS = 4_000
229
+
230
+
231
+ def judge_context(person_texts: Sequence[str], budget: int = JUDGE_CONTEXT_CHARS) -> str:
232
+ """The person's turns as the judge's `<context>`, in order, the NEWEST
233
+ kept whole within `budget` and older ones dropped first — a tetrad drawn
234
+ now answers what was said last. Empty when nothing was said."""
235
+ kept: list[str] = []
236
+ used = 0
237
+ for text in reversed([t.strip() for t in person_texts if t and t.strip()]):
238
+ if kept and used + len(text) > budget:
239
+ break
240
+ kept.append(text)
241
+ used += len(text)
242
+ return "\n\n".join(reversed(kept))
243
+
244
+
218
245
  def view_sketch_prompt(
219
246
  focus: Optional[str], max_words: int, thesis: Optional[str] = None
220
247
  ) -> str:
@@ -239,6 +266,16 @@ def view_sketch_prompt(
239
266
  (`THESIS_IS_FIXED`): the stance ask comes OFF — there is nothing to find —
240
267
  and the view is asked for the ONE tension that position stands in, because
241
268
  a pinned T says nothing about any other tension and a card shows one.
269
+
270
+ ONE request for every draw of a best-of-N, by measurement (2026-10-13,
271
+ `tests/e2e/probe_view_turn_ask.py`, pinned, 40 utterances, two arms the
272
+ same day): asking draw 2..N for a different opposition made every card's
273
+ spares a different position (75/80 positions) and cost the WINNER —
274
+ coherence 19/40 against 27/40 with identical draws, positions 34 against
275
+ 37, 1.26x the seconds — so the draws stay a re-roll of one request and
276
+ the spares stay the same tension reworded. The rejected ask and its
277
+ figures: `docs/dev-notes/antithesis-selection.md`, "Best-of-3 drew the
278
+ same tetrad three times".
242
279
  """
243
280
  pinned = (thesis or "").strip()
244
281
  focus_line = (
@@ -288,8 +288,10 @@ class Settings(BaseModel):
288
288
  # so this is a ceiling on one generation; on a streamed round it is the gap
289
289
  # between two chunks, and a thinking turn streams its deltas (the
290
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
291
+ # non-streamed case only. Since `llm_stream_awaited_calls` (below) every
292
+ # awaited call is streamed too, so this bounds the wait for the first
293
+ # content; a hang is caught earlier, when the response fails to open.
294
+ # 120s clears every unstalled generation in the archive (the longest measured conversational rounds sit under a minute on
293
295
  # Sonnet 5 with thinking) while cutting a stall from 600s to two minutes. A
294
296
  # host whose calls are short sets it lower: the first app's draws run ~15s,
295
297
  # texts ~8s, structured calls ~3s on Sonnet 5.5, and it runs at 30s. Too
@@ -299,6 +301,21 @@ class Settings(BaseModel):
299
301
  # timeout, when the provider registers.
300
302
  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
303
 
304
+ # Every awaited Bedrock call (`BedrockAnthropicProvider._create_async`) is
305
+ # sent as a stream and assembled into the same `Message` before Mirascope
306
+ # decodes it, so a stall is caught where it happens instead of at the whole
307
+ # read timeout: the response must OPEN within 5s (`_STREAM_HEADERS_TIMEOUT_S`),
308
+ # content must start within `llm_read_timeout_s`, and a gap past 5s once it
309
+ # flows is a stall. Measured on the first app's promise run (2026-10-12,
310
+ # Sonnet 5.5, 59 cases, read timeout 20s): ~5.5% of calls hang, ALL before
311
+ # the response opens, while healthy calls open in a median 1.2s whatever
312
+ # their size; against a same-day baseline, cards drawn 43 -> 51, cards past
313
+ # the app's deadline 9 -> 0, p90 53s -> 33s, cost unchanged. The switch
314
+ # exists as an off switch for a model or provider the measurement did not
315
+ # cover (`docs/dev-notes/streaming-latency-and-retry.md`, "Hangs are caught
316
+ # at the opening"). Per request.
317
+ llm_stream_awaited_calls: bool = Field(default=True, description="Send awaited provider calls as streams and assemble the message, so a hung call is caught when the response fails to open (5s) rather than at the whole read timeout. On by default; off only for a model or provider where streaming misbehaves.")
318
+
302
319
  @classmethod
303
320
  def from_partial(cls, partial_settings: Optional[Settings] = None) -> Self:
304
321
  """
@@ -377,5 +394,6 @@ class Settings(BaseModel):
377
394
  **_env_thinking_level(),
378
395
  llm_connect_timeout_s=float(os.getenv("DIALEXITY_LLM_CONNECT_TIMEOUT_S", 30.0)),
379
396
  llm_read_timeout_s=float(os.getenv("DIALEXITY_LLM_READ_TIMEOUT_S", 120.0)),
397
+ llm_stream_awaited_calls=os.getenv("DIALEXITY_LLM_STREAM_AWAITED_CALLS", "true").lower() == "true",
380
398
  effect_log_dir=os.getenv("DIALEXITY_GRAPH_LOG_DIR"),
381
399
  )
@@ -1,5 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
4
+ import logging
5
+ import time
3
6
  from collections.abc import Mapping, Sequence
4
7
  from typing import TYPE_CHECKING, Any, cast
5
8
 
@@ -18,6 +21,7 @@ from typing_extensions import Unpack
18
21
  from dialectical_framework.enums.di import DI
19
22
  from dialectical_framework.exceptions.provider_errors import ModelRefusal
20
23
  from dialectical_framework.settings import Settings
24
+ from dialectical_framework.settings_context import get_current_settings
21
25
  from dialectical_framework.utils.format_compat import (
22
26
  learn_format_mode_from_error,
23
27
  structured_output_format,
@@ -375,6 +379,135 @@ def raise_on_refusal(response: Any, model_name: str) -> None:
375
379
  )
376
380
 
377
381
 
382
+ logger = logging.getLogger(__name__)
383
+
384
+ #: Once a streamed awaited call has started writing content, the longest it may
385
+ #: go without another event before it counts as a stall. Generation is a steady
386
+ #: flow of deltas, so a silence this long mid-content is a hang rather than a
387
+ #: slow token. Before the first content delta the wait is the read timeout:
388
+ #: that window holds queueing, prefill and (where a call thinks) the thinking
389
+ #: block. Fired once in 2,700 streamed calls on the first app's promise runs,
390
+ #: so it is a backstop; the measured hangs are all caught at the opening.
391
+ _STREAM_GAP_TIMEOUT_S = 5.0
392
+
393
+ #: How long a streamed awaited call may wait for the response to OPEN (headers,
394
+ #: `message_start`) before it counts as a stall. Measured on the first app's
395
+ #: promise run (2026-10-12, Sonnet 5.5, 890 streamed calls): every stall (48,
396
+ #: 5.4%) hung before the headers, while healthy calls opened at a median 1.2s,
397
+ #: p99 3.6s, whatever their size (6 of 842 past 5s). Streamed, the opening is
398
+ #: the one wait that does not grow with the output, so it is where a hang is
399
+ #: caught: here at 5s rather than at the whole read timeout.
400
+ _STREAM_HEADERS_TIMEOUT_S = 5.0
401
+
402
+ #: Deltas that are the answer being written, as opposed to thinking or
403
+ #: bookkeeping. The SDK's stream yields both the raw `content_block_delta` and
404
+ #: a synthesized event per delta kind.
405
+ _CONTENT_DELTAS = frozenset({"text_delta", "input_json_delta"})
406
+ _CONTENT_EVENTS = frozenset({"text", "input_json"})
407
+
408
+
409
+ class StreamReadTimeout(httpx.ReadTimeout):
410
+ """A streamed awaited call went quiet past its budget.
411
+
412
+ Subclasses `httpx.ReadTimeout` so `use_brain._is_stall_error` reads it as a
413
+ stall (the name ends in `ReadTimeout`) and re-asks it once, exactly like a
414
+ socket read timeout on the non-streamed path.
415
+ """
416
+
417
+
418
+ def _is_content_event(event: Any) -> bool:
419
+ kind = getattr(event, "type", None)
420
+ if kind in _CONTENT_EVENTS:
421
+ return True
422
+ if kind == "content_block_delta":
423
+ return getattr(getattr(event, "delta", None), "type", None) in _CONTENT_DELTAS
424
+ return False
425
+
426
+
427
+ async def _stream_to_message(
428
+ manager: _LearningStreamManager, model_name: str, read_timeout_s: float
429
+ ) -> AnthropicMessage:
430
+ """Consume one stream and return the assembled `Message`, policing silence.
431
+
432
+ Three budgets: the response must OPEN within `_STREAM_HEADERS_TIMEOUT_S`
433
+ (where the measured hangs are), the first content must follow within the
434
+ read timeout (queueing, prefill, thinking), and once content flows a gap
435
+ longer than `_STREAM_GAP_TIMEOUT_S` is a stall.
436
+
437
+ The provider's `ping`s do not count as progress: a stream that only pings
438
+ is a hang the socket's read timeout would never see. Every stall is logged
439
+ with the phase it happened in: the phase is what chose the opening as the
440
+ place to police (all 137 measured hangs were there), and a shift in it is
441
+ the first sign that choice needs revisiting.
442
+ """
443
+ loop = asyncio.get_running_loop()
444
+ started = time.monotonic()
445
+ headers_budget = min(_STREAM_HEADERS_TIMEOUT_S, read_timeout_s)
446
+ try:
447
+ stream = await asyncio.wait_for(manager.__aenter__(), timeout=headers_budget)
448
+ except asyncio.TimeoutError as e:
449
+ logger.warning(
450
+ "Stream stalled on %s before headers (%.1fs)",
451
+ model_name, time.monotonic() - started,
452
+ )
453
+ raise StreamReadTimeout(
454
+ f"stream did not open within {headers_budget:.0f}s"
455
+ ) from e
456
+ except Exception as e:
457
+ if type(e).__name__.endswith(("TimeoutError", "ReadTimeout")):
458
+ logger.warning(
459
+ "Stream stalled on %s before headers (%.1fs)",
460
+ model_name, time.monotonic() - started,
461
+ )
462
+ raise
463
+ events = 0
464
+ first_content_s: float | None = None
465
+ try:
466
+ iterator = stream.__aiter__()
467
+ deadline = loop.time() + read_timeout_s
468
+ while True:
469
+ try:
470
+ event = await asyncio.wait_for(
471
+ iterator.__anext__(), timeout=max(deadline - loop.time(), 0.0)
472
+ )
473
+ except StopAsyncIteration:
474
+ break
475
+ except Exception as e:
476
+ timed_out_here = isinstance(e, asyncio.TimeoutError)
477
+ if not timed_out_here and not type(e).__name__.endswith(
478
+ ("Timeout", "TimeoutError")
479
+ ):
480
+ raise
481
+ elapsed = time.monotonic() - started
482
+ phase = (
483
+ "before first content"
484
+ if first_content_s is None
485
+ else f"mid-content (first content at {first_content_s:.1f}s)"
486
+ )
487
+ logger.warning(
488
+ "Stream stalled on %s %s: %.1fs, %d events",
489
+ model_name, phase, elapsed, events,
490
+ )
491
+ if not timed_out_here:
492
+ raise
493
+ raise StreamReadTimeout(
494
+ f"stream went quiet {phase} after {elapsed:.1f}s and {events} events"
495
+ ) from e
496
+ if getattr(event, "type", None) == "ping":
497
+ continue
498
+ events += 1
499
+ if first_content_s is None and _is_content_event(event):
500
+ first_content_s = time.monotonic() - started
501
+ budget = read_timeout_s if first_content_s is None else _STREAM_GAP_TIMEOUT_S
502
+ deadline = loop.time() + budget
503
+ message = await stream.get_final_message()
504
+ except BaseException as e:
505
+ await manager.__aexit__(type(e), e, e.__traceback__)
506
+ raise
507
+ await manager.__aexit__(None, None, None)
508
+ return message
509
+
510
+
378
511
  class BedrockAnthropicProvider(AnthropicProvider):
379
512
  """Mirascope v2 provider that routes through AnthropicBedrock client (async-native).
380
513
 
@@ -394,6 +527,7 @@ class BedrockAnthropicProvider(AnthropicProvider):
394
527
  ) -> None:
395
528
  # Skip super().__init__() — parent creates Anthropic/AsyncAnthropic clients we don't need
396
529
  timeout = _client_timeout(connect_timeout_s, read_timeout_s)
530
+ self._read_timeout_s = timeout.read or 120.0
397
531
  self.client = AnthropicBedrock(timeout=timeout, max_retries=_SDK_MAX_RETRIES)
398
532
  self.async_client = AsyncAnthropicBedrock(
399
533
  timeout=timeout, max_retries=_SDK_MAX_RETRIES
@@ -468,8 +602,20 @@ class BedrockAnthropicProvider(AnthropicProvider):
468
602
  models reject. `with_thinking_compat` translates by model name; if the
469
603
  name heuristic is wrong the 400 itself says which shape is wanted, so
470
604
  one retry is both sufficient and self-correcting. See thinking_compat.
605
+
606
+ With `Settings.llm_stream_awaited_calls` the same request goes out as a
607
+ stream and comes back as the same assembled `Message`, so a stall is
608
+ caught by the gap between chunks (`_stream_to_message`) instead of by a
609
+ ceiling on the whole generation. The stream manager learns the thinking
610
+ shape the same way.
471
611
  """
472
612
  model_name = kwargs["model"]
613
+ if get_current_settings().llm_stream_awaited_calls:
614
+ return await _stream_to_message(
615
+ _LearningStreamManager(self.async_client, kwargs, params),
616
+ model_name,
617
+ self._read_timeout_s,
618
+ )
473
619
  try:
474
620
  return await self.async_client.messages.create(
475
621
  **with_thinking_compat(model_name, kwargs, params)