dialectical-framework 2.0.11__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/PKG-INFO +1 -1
  2. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/pyproject.toml +1 -1
  3. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/consultant/consultant.py +27 -2
  4. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/aspect_generation.py +28 -3
  5. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/transformation_sketch.py +32 -1
  6. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/view_sketch.py +72 -11
  7. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/settings.py +20 -2
  8. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/bedrock_provider.py +146 -0
  9. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/LICENSE +0 -0
  10. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/PATENTS +0 -0
  11. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/README.md +0 -0
  12. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/__init__.py +0 -0
  13. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/__init__.py +0 -0
  14. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  15. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  16. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
  17. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/migration.py +0 -0
  18. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
  19. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  20. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  21. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  22. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  23. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  24. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  25. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
  26. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
  27. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
  28. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  29. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/agent_context.py +0 -0
  30. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  31. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  32. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  33. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  34. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  35. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  36. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  37. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  38. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
  39. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  40. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
  41. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  42. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  43. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/app_spec.py +0 -0
  44. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/apps.py +0 -0
  45. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
  46. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  47. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/execution_report.py +0 -0
  48. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  49. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  50. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  51. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  52. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  53. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  54. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  55. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  56. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  57. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  58. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  59. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  60. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  61. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  62. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  63. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
  64. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  65. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  66. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  67. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  68. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  69. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  70. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  71. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  72. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  73. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  74. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/stream_events.py +0 -0
  75. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/toolsets.py +0 -0
  76. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/agents/turn_timing.py +0 -0
  77. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/__init__.py +0 -0
  78. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  79. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  80. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/add_input.py +0 -0
  81. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  82. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  83. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  84. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  85. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  86. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  87. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  88. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/build_status.py +0 -0
  89. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  90. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  91. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  92. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  93. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  94. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  95. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  96. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  97. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  98. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  99. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  100. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  101. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  102. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  103. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
  104. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  105. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  106. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/discard.py +0 -0
  107. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  108. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  109. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  110. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  111. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  112. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/record_decision.py +0 -0
  113. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  114. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/source_digest.py +0 -0
  115. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  116. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  117. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  118. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  119. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  120. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/tetrad_candidates.py +0 -0
  121. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
  122. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/tetrad_sketch.py +0 -0
  123. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  124. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  125. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  126. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  127. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/enums/__init__.py +0 -0
  128. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/enums/causality_preset.py +0 -0
  129. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/enums/di.py +0 -0
  130. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/events/__init__.py +0 -0
  131. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/events/graph_event.py +0 -0
  132. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  133. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/events/progress_event.py +0 -0
  134. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/exceptions/__init__.py +0 -0
  135. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  136. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/exceptions/provider_errors.py +0 -0
  137. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  138. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/__init__.py +0 -0
  139. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  140. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  141. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  142. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  143. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  144. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  145. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  146. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  147. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  148. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  149. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/case.py +0 -0
  150. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  151. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/decision.py +0 -0
  152. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  153. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  154. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/input.py +0 -0
  155. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  156. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/note.py +0 -0
  157. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  158. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  159. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  160. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  161. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  162. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  163. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  164. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  165. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  166. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  167. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  168. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  169. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  170. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  171. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  172. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  173. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  174. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  175. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  176. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  177. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  178. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  179. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  180. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  181. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  182. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  183. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  184. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  185. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  186. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  187. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  188. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  189. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  190. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/rendering.py +0 -0
  191. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  192. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  193. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  194. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  195. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  196. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  197. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  198. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
  199. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  200. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  201. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  202. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  203. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  204. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  205. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/scope_context.py +0 -0
  206. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  207. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/views.py +0 -0
  208. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  209. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  210. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/protocols/__init__.py +0 -0
  211. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/protocols/has_config.py +0 -0
  212. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  213. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/py.typed +0 -0
  214. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/settings_context.py +0 -0
  215. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/__init__.py +0 -0
  216. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/async_drain.py +0 -0
  217. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/call_census.py +0 -0
  218. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/chunking.py +0 -0
  219. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/concurrency.py +0 -0
  220. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/dc_replace.py +0 -0
  221. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  222. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/edge_context.py +0 -0
  223. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/effect_logger.py +0 -0
  224. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/format_compat.py +0 -0
  225. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/input_context.py +0 -0
  226. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/order_transitions.py +0 -0
  227. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/progress.py +0 -0
  228. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/retry_accounting.py +0 -0
  229. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  230. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/thinking_compat.py +0 -0
  231. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/src/dialectical_framework/utils/use_brain.py +0 -0
  232. {dialectical_framework-2.0.11 → dialectical_framework-2.1.0}/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.11
3
+ Version: 2.1.0
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.11"
3
+ version = "2.1.0"
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"
@@ -381,7 +381,10 @@ class Consultant:
381
381
  return self._conversation._messages
382
382
 
383
383
  async def exploration_view(
384
- self, focus: Optional[str] = None, attempts: Optional[int] = None
384
+ self,
385
+ focus: Optional[str] = None,
386
+ attempts: Optional[int] = None,
387
+ thesis: Optional[str] = None,
385
388
  ) -> ExplorationView:
386
389
  """The structure of this conversation as a view — drawn by this head.
387
390
 
@@ -402,6 +405,17 @@ class Consultant:
402
405
  think and building a tetrad in one shot is the heaviest reasoning a
403
406
  structured call is asked to do here.
404
407
 
408
+ `thesis` pins T to the host's own text, VERBATIM: the request says so
409
+ (`view_sketch.THESIS_IS_FIXED`) and the given words are then written
410
+ back over whatever each draw wrote, so the guarantee is code and not
411
+ compliance. For a host that shows the person their own sentence as
412
+ their position, a seven-word recomposition of it is a different
413
+ sentence. The rest of the tetrad is still built — and still built
414
+ around the pinned position, which is why the request carries the text
415
+ too rather than relying on the overwrite alone. Not the same contract
416
+ as `SketchTetrad(thesis=)`, which asks for the position condensed to
417
+ `component_length` because the graph stores a headline.
418
+
405
419
  Terminology-free by construction (no hash, alias or score exists),
406
420
  unchecked by construction (no HS gate, no validation, nothing
407
421
  persisted). Not a tool: the model cannot call this, and the prompt's
@@ -433,7 +447,9 @@ class Consultant:
433
447
  # utterances; the first app had measured the same 4.6 calls and +17 s).
434
448
  # The request->prose pair `history_ask` keeps OUT of the history is the
435
449
  # same trap; the long request as the last turn is what holds the format.
436
- request = view_sketch_prompt(focus, self._conversation.settings.component_length)
450
+ request = view_sketch_prompt(
451
+ focus, self._conversation.settings.component_length, thesis=thesis
452
+ )
437
453
 
438
454
  async def draw() -> ViewSketchDto:
439
455
  # Each draw works on a COPY of the history: the turns run in
@@ -472,6 +488,15 @@ class Consultant:
472
488
  )
473
489
  if not sketches:
474
490
  raise failures[0]
491
+ # The pin, enforced: every draw's first tension carries the host's own
492
+ # words as T, so the winner and any runner-up a host serves later all
493
+ # show the sentence the person typed. Done before selection, so the
494
+ # coherence check judges the tetrad the host will actually get.
495
+ pinned = (thesis or "").strip()
496
+ if pinned:
497
+ for drawn_sketch in sketches:
498
+ if drawn_sketch.tensions:
499
+ drawn_sketch.tensions[0].thesis = pinned
475
500
  # With several draws the framework's own coherence check picks the one
476
501
  # whose FIRST tension holds best (`concerns/tetrad_candidates.py`); a
477
502
  # draw with no complete first tension sorts last. The card a blindspot
@@ -165,10 +165,35 @@ Generate aspect statements that fit the semantic structure."""
165
165
  # — small, and not re-measured at the archive's sample size. Do not re-add a
166
166
  # worked plus example without measuring BOTH coherence and restatement
167
167
  # (`tests/e2e/probe_aspect_variants.py`).
168
+ #
169
+ # Re-aimed on 2026-10-12, by measurement, from "supplies what the other pole is
170
+ # for" to "leaves it possible". The old wording was the take-up half stated a
171
+ # third time, and with a DECIDED thesis ("I'm not inviting my sister — done")
172
+ # "A developed so that it supplies what T is for" IS the middle path: A+ came
173
+ # back as the compromise ("Leave a path back that she must earn"), and the
174
+ # transformation's Ac+, which turns T- into A+, then faithfully handed the
175
+ # person back the thing they had refused (reported by the first app, promise
176
+ # eval v12, 3 of 7 decided cases failing in every rep). On 16 decided stances
177
+ # x 2 reps x 3 arms, Sonnet 5.5 (`tests/e2e/probe_decided_stance.py`,
178
+ # `decided_stance-20261012-090438.json`): A+ a good A ITSELF is for, 39% ->
179
+ # 94% (paired +55, 95% CI +33..+77), compromise pluses gone (plus_relation
180
+ # `distinct` 69% -> 100%), and coherence ROSE, 59% -> 91% (+31, +9..+54) —
181
+ # which is the theory's own reading, since a plus that carries the other pole
182
+ # cannot satisfy "T+ without A+ yields T-". The arm that only reversed the
183
+ # relation reached 75% / +12 CC; what took it to 94% was adding "a position,
184
+ # not a move" (`view_sketch.TETRAD_BUILD_PROCEDURE` step 1), so the two
185
+ # sentences ship together. Not the same lever as the two spent ones: pushing
186
+ # take-up HARDER (the `*_takes_up` fields, 2026-10-02) cost coherence 25/40 ->
187
+ # 15/40, and a hedged-parent CHECK (2026-09-30) moved nothing.
188
+ # `ASPECT_DEFINITIONS` still states the relation the old way and was in both
189
+ # arms' context — unchanged on purpose; editing it is a new measurement.
168
190
  PLUS_RESTATEMENT_CHECK = (
169
- "Restated parent: if a plus only names what its own parent already "
170
- "delivers, rewrite it so the parent stays the generative act while its "
171
- "result also supplies what the other pole is for."
191
+ "Blended parent: a plus is its own parent developed, never the two sides "
192
+ "combined into one arrangement — not the parent made conditional on the "
193
+ "other pole, not a partial, scheduled or trial version of it, not the "
194
+ "middle between them. If a plus only names what its own parent already "
195
+ "delivers, rewrite it so the parent stays the generative act and what the "
196
+ "other pole is for is left possible by it."
172
197
  )
173
198
 
174
199
  def is_axis_name(axis: Optional[str]) -> bool:
@@ -116,9 +116,40 @@ from dialectical_framework.protocols.has_config import SettingsAware
116
116
  #: that writes the way through a tension in its own voice (the first app's
117
117
  #: card) — so none of them can restate the theory differently.
118
118
  TRANSFORMATION_POSITIONS = """1. The action (Ac): the concrete move that carries T toward A. The reflection (Re): the re-reading that carries A back toward T. These are the two poles of the transformation.
119
- 2. Each developed well — Ac+ turns T's trap (T-) into A's strength (A+); Re+ turns A's trap (A-) into T's strength (T+).
119
+ 2. Each developed well — Ac+ turns T's trap (T-) into A's strength (A+); Re+ turns A's trap (A-) into T's strength (T+). Where T is something the person has DECIDED or refused, Ac and Ac+ are that decision carried out: what A is for shapes HOW they carry it out, never whether or how much. This governs the action and the action developed well only — Ac- still ends in A's trap, as point 3 says.
120
120
  3. Each overdeveloped one-sidedly — Ac- is the good action without the good reflection (Ac+ without Re+), which carries T's strength (T+) into A's trap (A-); Re- is the good reflection without the good action (Re+ without Ac+), which carries A's strength (A+) into T's trap (T-). They are the ACTION and the REFLECTION gone wrong: they END in the traps but are not the traps restated — a path is not its destination. Ac+ contradicts Re-, and Re+ contradicts Ac-."""
121
121
 
122
+ #: THE DECIDED POSITION (2026-10-12). Point 2's second sentence, and the only
123
+ #: part of this text that knows anything about the person's state. The first app
124
+ #: reported the framework handing people back exactly what they had refused:
125
+ #: given "Im not inviting my sister to the wedding … done.", Ac+ came back as
126
+ #: "Give her a clear route back: apologise to Marco and she's welcome." Repairing
127
+ #: A+ upstream (the compromise plus, `antithesis-selection.md`, same day) fixed
128
+ #: the CORNER and not the move — `decision_stands` 62% before and after — so this
129
+ #: layer has its own site, and it is not point 1's "carries T toward A":
130
+ #: rewording that moved nothing (-3 paired, `tests/e2e/probe_decided_move.py`).
131
+ #: What moved it is naming the state: 53% -> 88% of actions keeping the decision
132
+ #: (+34, +18..+51, 32 frozen decided tetrads).
133
+ #:
134
+ #: **The scope clause is load-bearing, measured twice.** Whichever minus the rule
135
+ #: is allowed to reach is the one that breaks. Stated unscoped here it kept 32 of
136
+ #: 32 decisions and broke Ac- (-25, resolved): told the action must not dilute
137
+ #: toward A, the writer wrote Ac- as the decision carried out COLDLY, which lands
138
+ #: in T-, not A-. Moved onto the `action` / `ac_plus` field descriptions it scored
139
+ #: the best endpoint of the three (+47) and broke Re- instead (-32, resolved).
140
+ #: Naming what it does not govern is what left both landings alone.
141
+ #:
142
+ #: Price, on this text's own gate (`probe_transformation_sketch.py`, 25 tetrads
143
+ #: x 2 reps, arm `no_decided_rule`): landings intact (Ac- +2, Re- -4, noise),
144
+ #: validity composites within noise (-8, -10), S- third failure +8, and
145
+ #: **`ac_own` 100% -> 90% (-10, -18..-2, resolved)** — the second accepted trade
146
+ #: on that read, after the scaffold fields' 100% -> 93%. Named rather than
147
+ #: swallowed: a third one needs an argument.
148
+ #:
149
+ #: What the sentence deliberately does NOT say is that a diluted action IS Ac-.
150
+ #: That is a theory claim nothing here has checked, and the archive's record is
151
+ #: that a gloss hardened into a rule gets argued with rather than obeyed.
152
+ #:
122
153
  #: What the developments produce together. The S- sentences are the measured
123
154
  #: fix (`one-shot-transformation.md`: third-trap 4/17 → 12/17 on graph-built
124
155
  #: wheels once the synthesis prompt carried them) and are kept word for word,
@@ -111,9 +111,11 @@ class ViewSketchPerspectiveDto(BaseModel):
111
111
  )
112
112
  t_plus: str = Field(
113
113
  description=(
114
- "T+ — the THESIS developed constructively, so that it also strengthens "
115
- "what A offers. Derived from T; the positive end of the axis above. "
116
- "Empty when not drawn."
114
+ "T+ — the THESIS developed constructively, so that what A is for stays "
115
+ "possible alongside it. T's own side developed — never T blended with A, "
116
+ "made conditional on it, or the middle between the two. Derived from T; "
117
+ "the positive end of the axis above. A position held, not a move to "
118
+ "make. Empty when not drawn."
117
119
  )
118
120
  )
119
121
  a_minus: str = Field(
@@ -131,9 +133,11 @@ class ViewSketchPerspectiveDto(BaseModel):
131
133
  )
132
134
  a_plus: str = Field(
133
135
  description=(
134
- "A+ — the ANTITHESIS developed constructively, so that it also strengthens "
135
- "what T offers. Derived from A; the positive end of the axis above. "
136
- "Empty when not drawn."
136
+ "A+ — the ANTITHESIS developed constructively, so that what T is for "
137
+ "stays possible alongside it. A's own side developed — never A blended "
138
+ "with T, made conditional on it, or the middle between the two. Derived "
139
+ "from A; the positive end of the axis above. A position held, not a move "
140
+ "to make. Empty when not drawn."
137
141
  )
138
142
  )
139
143
  t_minus: str = Field(
@@ -162,6 +166,16 @@ class ViewSketchDto(BaseModel):
162
166
  #: the graph path gives `AspectGeneration._tetrad_prompt`, stated once here and
163
167
  #: shared by the view turn and `concerns/tetrad_sketch.py`, so the two
164
168
  #: one-shot builders cannot drift apart.
169
+ #:
170
+ #: Step 1's two measured sentences (2026-10-12, `probe_decided_stance.py`; the
171
+ #: story is on `PLUS_RESTATEMENT_CHECK`, which carries the same rule as the
172
+ #: step-3 check): a plus LEAVES the other pole's good possible rather than
173
+ #: delivering it, and an aspect is a POSITION rather than a move. The second
174
+ #: is what took A+ from 75% to 94% own-strength and coherence from 72% to 91%
175
+ #: on decided stances — the failures it removes are imperatives ("Leave a path
176
+ #: back that she must earn"), and a move that answers both sides at once is a
177
+ #: compromise by construction. It is also where the layering is stated to the
178
+ #: writer: positions live in the tetrad, moves in the Transformation.
165
179
  #: What the thesis IS, for a writer that finds it in free words. Measured
166
180
  #: without it (`tests/e2e/probe_question_entry.py`, 2026-10-04, 20 questions
167
181
  #: through the view turn): the tetrad was statement-grade (CC 17/20, a genuine
@@ -181,12 +195,29 @@ THESIS_IS_A_STANCE = """The thesis (T) is a STANCE: something the person holds,
181
195
  TETRAD_BUILD_PROCEDURE = f"""Building a tetrad, when you build one — as its two diagonal contradiction pairs (T+ vs A-, A+ vs T-).
182
196
  Each aspect has one fixed parent: T+ and T- develop T; A+ and A- develop A.
183
197
  For each pair:
184
- 1. Derive each aspect from ITS OWN parent — a plus develops that parent so it also takes up what the other pole offers; a minus overdevelops that parent one-sidedly, with the other pole absent.
198
+ 1. Derive each aspect from ITS OWN parent — a plus develops that parent so that what the other pole is for stays POSSIBLE alongside it, not so that it delivers it; a minus overdevelops that parent one-sidedly, with the other pole absent. Each aspect is a POSITION held or a good someone values, never a move to make: the moves come later, from the way through the tension.
185
199
  2. Then name the **axis** — the single dimension on which the two aspects are opposite ends, so they cannot both hold at once.
186
200
  3. Re-read both aspects against step 1 for two distinct failures. (a) Wrong parent: if one is really the OTHER parent developed, rewrite it from its own parent. (b) {PLUS_RESTATEMENT_CHECK}"""
187
201
 
188
202
 
189
- def view_sketch_prompt(focus: Optional[str], max_words: int) -> str:
203
+ #: What the thesis is when the HOST already has it and wants it kept word for
204
+ #: word. Distinct from `tetrad_sketch_prompt`'s given-thesis line, which asks
205
+ #: for the position "worded in the person's own terms, {max_words} words or
206
+ #: fewer" — a condensation, which is right for `anchor` (the model named the
207
+ #: position, the graph stores a headline) and wrong for a host that shows the
208
+ #: person their OWN sentence as their position (the first app, 2026-10-12: the
209
+ #: card displays the typed words while T was a seven-word recomposition of
210
+ #: them). So this one forbids the rewording the other one invites, and the
211
+ #: framework does not rely on it being obeyed: `Consultant.exploration_view`
212
+ #: writes the given text back over the drawn one (the pin is in code; this
213
+ #: text only stops the writer building the rest of the tetrad around a position
214
+ #: it has quietly reworded).
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
+
217
+
218
+ def view_sketch_prompt(
219
+ focus: Optional[str], max_words: int, thesis: Optional[str] = None
220
+ ) -> str:
190
221
  """The structured turn's request, over the consultant's own conversation.
191
222
 
192
223
  Two instructions in one, because the ask is one or the other and the model
@@ -203,12 +234,42 @@ def view_sketch_prompt(focus: Optional[str], max_words: int) -> str:
203
234
  (docs/dev-notes/antithesis-selection.md, 2026-10-02). The thesis is asked
204
235
  for as a STANCE (`THESIS_IS_A_STANCE`): on 20 questions the turn echoed the
205
236
  question as T in 6 before the ask was here (the alsotrue app repo, docs/lab-notes-entry.md).
237
+
238
+ With `thesis` the host has the position already and wants it kept verbatim
239
+ (`THESIS_IS_FIXED`): the stance ask comes OFF — there is nothing to find —
240
+ and the view is asked for the ONE tension that position stands in, because
241
+ a pinned T says nothing about any other tension and a card shows one.
206
242
  """
243
+ pinned = (thesis or "").strip()
207
244
  focus_line = (
208
245
  f"What to show: {focus}\n\n"
209
246
  if focus and focus.strip()
210
247
  else "What to show: the tensions this conversation has worked out so far.\n\n"
211
248
  )
249
+ scope_line = (
250
+ "- ONE tension: the one the given position below stands in. How much of "
251
+ "it is drawn is still the ask's to say, by the rule above."
252
+ if pinned
253
+ else f"- At most {VIEW_SKETCH_MAX_PERSPECTIVES} tensions, most central "
254
+ "first. Nothing at all rather than a tension the conversation does not "
255
+ "support — a turn that carries no stance (a request for information) "
256
+ "supports none."
257
+ )
258
+ thesis_section = (
259
+ f'{THESIS_IS_FIXED}\n\nThe position: "{pinned}"'
260
+ if pinned
261
+ else THESIS_IS_A_STANCE
262
+ )
263
+ # The exception rides on the line that states the limit, not as a
264
+ # cross-reference from the section above it: a rule and its exception in
265
+ # two places is the competing-signal shape, and the limit is the last line
266
+ # the model reads.
267
+ length_exception = (
268
+ " The given position is the one exception: it stays exactly as it is, "
269
+ "whatever its length."
270
+ if pinned
271
+ else ""
272
+ )
212
273
  return f"""Draw the structure of this conversation as dialectical tetrads. Answer with ONE JSON object in the requested schema and nothing else — no prose. Earlier drawings in this conversation are recorded in prose; that is their record, not the format of this answer.
213
274
 
214
275
  {focus_line}{ASPECT_DEFINITIONS}
@@ -217,15 +278,15 @@ Rules for what goes in the view:
217
278
  - Show what this conversation has ESTABLISHED: a tension you named and the person took up is drawn as you both have it, in their terms. Do not re-derive what is already on the table.
218
279
  - Where the ask needs structure not yet worked out — a perspective for a thesis so far only stated, the antithesis of a position, the corners of a tension you only named — build it now, by the method below, and then draw it.
219
280
  - Where the ask stops short of a full tetrad (only the two poles, only the antitheses), leave the positions not asked for EMPTY. An empty corner is honest; an invented one is not.
220
- - At most {VIEW_SKETCH_MAX_PERSPECTIVES} tensions, most central first. Nothing at all rather than a tension the conversation does not support — a turn that carries no stance (a request for information) supports none.
281
+ {scope_line}
221
282
 
222
- {THESIS_IS_A_STANCE}
283
+ {thesis_section}
223
284
 
224
285
  {_OPPOSING_POSITION_ASK}
225
286
 
226
287
  {TETRAD_BUILD_PROCEDURE}
227
288
 
228
- Write every position in the person's own terms, {max_words} words or fewer each."""
289
+ Write every position in the person's own terms, {max_words} words or fewer each.{length_exception}"""
229
290
 
230
291
 
231
292
  # --- The view shaping (pure) ----------------------------------------------
@@ -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)