dialectical-framework 2.1.1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/PKG-INFO +2 -2
  2. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/pyproject.toml +8 -2
  3. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/consultant/consultant.py +23 -2
  4. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/antithesis_extraction.py +17 -4
  5. dialectical_framework-2.1.2/src/dialectical_framework/concerns/attribution_check.py +148 -0
  6. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/tetrad_candidates.py +118 -14
  7. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/tetrad_sketch.py +8 -1
  8. dialectical_framework-2.1.2/src/dialectical_framework/concerns/thesis_fit_check.py +165 -0
  9. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/view_sketch.py +97 -10
  10. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/views.py +9 -0
  11. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/bedrock_provider.py +211 -44
  12. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/format_compat.py +65 -1
  13. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/thinking_compat.py +21 -0
  14. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/use_brain.py +11 -1
  15. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/LICENSE +0 -0
  16. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/PATENTS +0 -0
  17. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/README.md +0 -0
  18. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/__init__.py +0 -0
  19. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/__init__.py +0 -0
  20. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  21. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  22. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
  23. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/migration.py +0 -0
  24. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
  25. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  26. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  27. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  28. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  29. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  30. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  31. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
  32. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
  33. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
  34. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  35. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/agent_context.py +0 -0
  36. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  37. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  38. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  39. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  40. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  41. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  42. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  43. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  44. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
  45. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  46. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
  47. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  48. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  49. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/app_spec.py +0 -0
  50. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/apps.py +0 -0
  51. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
  52. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  53. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/execution_report.py +0 -0
  54. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  55. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  56. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  57. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  58. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  59. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  60. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  61. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  62. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  63. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  64. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  65. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  66. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  67. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  68. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  69. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
  70. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  71. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  72. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  73. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  74. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  75. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  76. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  77. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  78. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  79. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  80. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/stream_events.py +0 -0
  81. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/toolsets.py +0 -0
  82. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/agents/turn_timing.py +0 -0
  83. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/__init__.py +0 -0
  84. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  85. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  86. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/add_input.py +0 -0
  87. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  88. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  89. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  90. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  91. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  92. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  93. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  94. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/build_status.py +0 -0
  95. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  96. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  97. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  98. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  99. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  100. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  101. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  102. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  103. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  104. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  105. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  106. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  107. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  108. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  109. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
  110. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  111. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  112. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/discard.py +0 -0
  113. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  114. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  115. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  116. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  117. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  118. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/record_decision.py +0 -0
  119. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  120. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/source_digest.py +0 -0
  121. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  122. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  123. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  124. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  125. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  126. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
  127. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  128. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  129. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  130. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/concerns/transformation_sketch.py +0 -0
  131. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  132. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/enums/__init__.py +0 -0
  133. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/enums/causality_preset.py +0 -0
  134. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/enums/di.py +0 -0
  135. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/events/__init__.py +0 -0
  136. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/events/graph_event.py +0 -0
  137. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  138. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/events/progress_event.py +0 -0
  139. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/exceptions/__init__.py +0 -0
  140. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  141. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/exceptions/provider_errors.py +0 -0
  142. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  143. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/__init__.py +0 -0
  144. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  145. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  146. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  147. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  148. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  149. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  150. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  151. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  152. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  153. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  154. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/case.py +0 -0
  155. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  156. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/decision.py +0 -0
  157. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  158. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  159. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/input.py +0 -0
  160. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  161. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/note.py +0 -0
  162. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  163. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  164. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  165. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  166. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  167. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  168. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  169. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  170. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  171. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  172. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  173. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  174. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  175. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  176. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  177. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  178. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  179. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  180. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  181. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  182. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  183. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  184. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  185. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  186. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  187. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  188. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  189. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  190. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  191. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  192. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  193. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  194. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  195. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/rendering.py +0 -0
  196. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  197. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  198. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  199. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  200. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  201. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  202. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  203. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
  204. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  205. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  206. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  207. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  208. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  209. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  210. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/scope_context.py +0 -0
  211. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  212. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  213. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  214. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/protocols/__init__.py +0 -0
  215. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/protocols/has_config.py +0 -0
  216. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  217. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/py.typed +0 -0
  218. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/settings.py +0 -0
  219. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/settings_context.py +0 -0
  220. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/__init__.py +0 -0
  221. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/async_drain.py +0 -0
  222. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/call_census.py +0 -0
  223. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/chunking.py +0 -0
  224. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/concurrency.py +0 -0
  225. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/dc_replace.py +0 -0
  226. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  227. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/edge_context.py +0 -0
  228. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/effect_logger.py +0 -0
  229. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/input_context.py +0 -0
  230. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/order_transitions.py +0 -0
  231. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/progress.py +0 -0
  232. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/retry_accounting.py +0 -0
  233. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  234. {dialectical_framework-2.1.1 → dialectical_framework-2.1.2}/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.1.1
3
+ Version: 2.1.2
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
@@ -32,7 +32,7 @@ Requires-Dist: broadcaster (>=0.3.1,<0.4.0)
32
32
  Requires-Dist: dependency-injector (>=4.48.2,<5.0.0)
33
33
  Requires-Dist: gqlalchemy (>=1.8.0,<2.0.0)
34
34
  Requires-Dist: langfuse (>=4.5.1,<5.0.0)
35
- Requires-Dist: mirascope[anthropic,openai,ops] (>=2.5.0,<3.0.0)
35
+ Requires-Dist: mirascope[anthropic,openai,ops] (>=2.8.2,<3.0.0)
36
36
  Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
37
37
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
38
38
  Project-URL: Homepage, https://github.com/dialexity/dialectical-framework
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "2.1.1"
3
+ version = "2.1.2"
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"
@@ -43,11 +43,17 @@ from = "src"
43
43
 
44
44
  [tool.poetry.dependencies]
45
45
  python = "^3.11"
46
+ # 2.8.2 is the FLOOR, not a preference: below it Mirascope's Anthropic encoder
47
+ # sends extended thinking as the legacy token budget, which every Claude 5 model
48
+ # rejects with a 400 — so a thinking structured call (the view turn, TetradSketch)
49
+ # cannot run on the native `anthropic/` provider at all. The framework's own
50
+ # `thinking_compat` covers this for `bedrock/` only. Measured 2026-10-26, story in
51
+ # docs/dev-notes/antithesis-selection.md.
46
52
  mirascope = {extras = [
47
53
  "openai",
48
54
  "anthropic",
49
55
  "ops"
50
- ], version = "^2.5.0"}
56
+ ], version = "^2.8.2"}
51
57
  python-dotenv = "^1.2.1"
52
58
  tabulate = "^0.9.0"
53
59
  dependency-injector = "^4.48.2"
@@ -432,6 +432,8 @@ class Consultant:
432
432
  import dataclasses
433
433
 
434
434
  from dialectical_framework.agents.advisor.migration import person_turns
435
+ from dialectical_framework.concerns.attribution_check import \
436
+ drop_unborne_holders
435
437
  from dialectical_framework.concerns.tetrad_candidates import (
436
438
  clamp_attempts, ranking, select_sketch)
437
439
 
@@ -505,6 +507,20 @@ class Consultant:
505
507
  for drawn_sketch in sketches:
506
508
  if drawn_sketch.tensions:
507
509
  drawn_sketch.tensions[0].thesis = pinned
510
+ # A pinned position is the host's own words for the
511
+ # person's own position: never someone else's.
512
+ drawn_sketch.tensions[0].thesis_held_by = ""
513
+ # The person's turns, for the judges below. Also the words a named
514
+ # holder is checked against.
515
+ words = judge_context(person_turns(history))
516
+ # A holder the words do not bear is dropped, in code, on every draw
517
+ # (`concerns/attribution_check.py`, 2026-10-27): the prompt rule
518
+ # halved the misquotation on pasted material and did not end it, and a
519
+ # runner-up served later must not carry one either. No call where no
520
+ # holder is named — the person's own words cost nothing here.
521
+ firsts_to_check = [s.tensions[0] for s in sketches if s.tensions]
522
+ for dropped in await drop_unborne_holders(firsts_to_check, words):
523
+ logger.info("A drawn holder was dropped: %s", dropped)
508
524
  # With several draws the framework's own coherence check picks the one
509
525
  # whose FIRST tension holds best (`concerns/tetrad_candidates.py`); a
510
526
  # draw with no complete first tension sorts last. The card a blindspot
@@ -521,9 +537,14 @@ class Consultant:
521
537
  judgeable = [i for i, t in enumerate(firsts) if t is not None and all(
522
538
  (getattr(t, k) or "").strip() for k in ("thesis", "antithesis", "t_plus", "t_minus", "a_plus", "a_minus"))]
523
539
  if len(judgeable) > 1:
540
+ # This writer CHOOSES the thesis, so a second judge reads each
541
+ # draw's T, A and A+ against the person's words — the claim
542
+ # they would sign, an opposition they have not granted
543
+ # (`tetrad_candidates.judge_fit`, 2026-10-20). With a pinned T
544
+ # the first read is the same on every draw and the second
545
+ # still tells them apart.
524
546
  _best, verdicts = await select_sketch(
525
- [firsts[i] for i in judgeable],
526
- context=judge_context(person_turns(history)),
547
+ [firsts[i] for i in judgeable], context=words, fit_against=words
527
548
  )
528
549
  order = ranking(verdicts)
529
550
  sketch = sketches[judgeable[order[0]]]
@@ -66,7 +66,17 @@ _AwaitedT = TypeVar("_AwaitedT")
66
66
  # because the previous ask ("an antithesis that represents Negation of the
67
67
  # thesis") was complied with exactly — and at the high rungs exact compliance
68
68
  # is a caricature (measured 19/19, `tests/probe_blindspot_paths.py`).
69
- _OPPOSING_POSITION_ASK = """The antithesis is a POSITION someone holds for its own value, that functionally opposes the role the thesis plays — at this rung's mechanism, in the thesis's own terms. It is not the thesis's absence or degradation restated as a stance. Example: for "Quit my job and start my own company", Negation is "Keep the security and craft of employed work", not "Never quit, stay employed forever"; the second is a caricature nobody holds, and a caricature cannot be developed constructively — rate such a candidate's tetrad potential low rather than dressing it up."""
69
+ #
70
+ # The second failure, the caricature's mirror (2026-10-20, the first app's
71
+ # handoff `docs/dev-notes/antithesis-selection.md`, "The hedge wins on coherence"): "in the thesis's own
72
+ # terms" was complied with exactly too, and when the thesis's own words NAME
73
+ # their opponent and concede it ("not only schools, but also …"), that
74
+ # opponent is the most literal answer — the writer drew it as A on Sonnet 5.5
75
+ # in every configuration, and the card handed the person their own clause as
76
+ # the blind spot they were not seeing. This ask was one-sided: tuned against
77
+ # the strawman end, it had nothing to say about the granted end. UNMEASURED
78
+ # as a rule; the shape is the caricature rule's, in the same place.
79
+ _OPPOSING_POSITION_ASK = """The antithesis is a POSITION someone holds for its own value, that functionally opposes the role the thesis plays — at this rung's mechanism, in the thesis's own terms. It is not the thesis's absence or degradation restated as a stance. Example: for "Quit my job and start my own company", Negation is "Keep the security and craft of employed work", not "Never quit, stay employed forever"; the second is a caricature nobody holds, and a caricature cannot be developed constructively — rate such a candidate's tetrad potential low rather than dressing it up. The mirror failure: an opposition the thesis's own words already name AND concede ("not only X but also Y", "X matters, but …") is seen, not missing — its constructive side is something the person has already granted, and a tetrad built on it shows them their own concession as a blind spot. Rate such a candidate's tetrad potential low as well, and oppose the claim they actually make."""
70
80
 
71
81
 
72
82
  # --- Extraction-specific DTOs ---
@@ -94,7 +104,9 @@ class ModePointResultDto(BaseModel):
94
104
  "A is a position someone holds for its own value, it can be developed so "
95
105
  "that it also strengthens what T is for (an A+ exists), and it has its own "
96
106
  "one-sided failure (an A-). Low: A is T's absence or degradation dressed as "
97
- "a position, or a caricature nobody would hold — nothing to develop."
107
+ "a position, a caricature nobody would hold — nothing to develop — or an "
108
+ "opposition the thesis's own words already name and concede, whose A+ the "
109
+ "person has granted."
98
110
  ),
99
111
  )
100
112
  arousal_label: str = Field(
@@ -132,7 +144,8 @@ class OptimumARankingDto(BaseModel):
132
144
  "functionally opposes the role the thesis plays; it can be developed so "
133
145
  "that it also strengthens what the thesis is for, and it has its own "
134
146
  "one-sided failure. Worst = the thesis's absence or degradation dressed "
135
- "as a stance, or a caricature nobody holds."
147
+ "as a stance, a caricature nobody holds, or an opposition the thesis's own "
148
+ "words already concede."
136
149
  )
137
150
  )
138
151
  reasoning: str = Field(
@@ -542,7 +555,7 @@ Thesis: "{thesis.prompt_text}"
542
555
  Candidates:
543
556
  {numbered}
544
557
 
545
- The best antithesis is the one most likely to yield a coherent tetrad with the thesis: a POSITION someone holds for its own value that functionally opposes the role the thesis plays — it can be developed constructively so that it also strengthens what the thesis is for, and it has its own one-sided failure. Rank lower any candidate that is the thesis's absence or degradation dressed as a stance, a caricature nobody would hold, or the thesis merely hedged. Return every number exactly once."""
558
+ The best antithesis is the one most likely to yield a coherent tetrad with the thesis: a POSITION someone holds for its own value that functionally opposes the role the thesis plays — it can be developed constructively so that it also strengthens what the thesis is for, and it has its own one-sided failure. Rank lower any candidate that is the thesis's absence or degradation dressed as a stance, a caricature nobody would hold, the thesis merely hedged, or an opposition the thesis's own words already name and concede. Return every number exactly once."""
546
559
  try:
547
560
  verdict = await self._conversation.isolate().submit(
548
561
  response_model=OptimumARankingDto, user_content=prompt
@@ -0,0 +1,148 @@
1
+ """
2
+ AttributionCheck: do the words bear the holder a drawn pole names?
3
+
4
+ WHY A CHECK IN CODE
5
+ ===================
6
+ The view turn names WHO holds each pole when it is not the person's own
7
+ (`ViewSketchPerspectiveDto.thesis_held_by` / `antithesis_held_by`, carried as
8
+ `PoleView.held_by`, since 2026-10-26), and a prompt rule says a party is
9
+ named only for a position its own words take (`view_sketch.HOLDER_FROM_THE_WORDS`,
10
+ 2026-10-27). Measured on 37 pieces of pasted material, two draws each, the
11
+ rule HALVED the misquotation and did not end it: about one draw in twenty
12
+ still named a party for words the text does not bear — a speaker given the
13
+ inverse of their own position, two sides swapped — and a screen shows that
14
+ as "the Minister's position" over words the Minister never said. Prompt
15
+ enforcement scales with compliance (CLAUDE.md, the machinery leak), so the
16
+ last step is the same shape as the thesis pin: enforced by code after the
17
+ draw, not asked for again.
18
+
19
+ WHAT IT DOES
20
+ ============
21
+ One structured call per NAMED holder, shown the person's words and one pole,
22
+ asked whether the text attributes that position to that party — stated in
23
+ their own words or reported as theirs (a role and the person in it are one
24
+ party). NOT attributed: the holder is dropped, so the pole reads as a built
25
+ opposition (A) or as unattributed (T) instead of a misquotation. Attributed:
26
+ kept. Never a replacement: the judge is not handed the right to put a
27
+ different name on the screen. Free where it matters most — on the person's
28
+ own words every holder is empty and no call is made.
29
+
30
+ Fail-soft the way the fit judge is: a failed judging KEEPS the holder. The
31
+ alternative, dropping on error, would turn "the Minister's position" into
32
+ "your position" on a provider hiccup, which is the 2026-10-26 defect back.
33
+ A missing holder is also what `held_by` means on the graph path, so a host
34
+ must already handle None.
35
+
36
+ THE VERDICT'S WORDING IS VERSION 2. Version 1 asked whether the text
37
+ "attributes this position" to the party and, applied to the 130 named
38
+ holders of the gate's WITH-arm draws, dropped 28 — 18 of the 27 the probe's
39
+ own judge had flagged, and 10 it had grounded: it read an elaboration as a
40
+ misquotation ("opponents of trophy hunting" refused "killing for pleasure is
41
+ wrong" because the text says only that the controversy is about the morality
42
+ of hunting for pleasure; a rhetorical question that takes a side was "no
43
+ stance"). A dropped true holder costs what this check exists to prevent in
44
+ reverse — "the Minister's position" shown as "your position" — so version 2
45
+ says a fair condensation of the stance the text gives them is theirs, and
46
+ NOT_ATTRIBUTED is no stance at all, the opposite side, or someone else's.
47
+
48
+ MEASURED (2026-10-27, `tests/e2e/rescore_attribution_check.py`): see
49
+ `docs/dev-notes/views.md`, "A name on words never said" — applied to the
50
+ saved WITH-arm draws of the holder-rule gate and then live on the 37-case
51
+ finding run.
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ import asyncio
57
+ import logging
58
+ from typing import Any, Literal, Sequence
59
+
60
+ from pydantic import BaseModel, Field
61
+
62
+ from dialectical_framework.agents.conversation_facilitator import \
63
+ ConversationFacilitator
64
+
65
+ logger = logging.getLogger(__name__)
66
+
67
+
68
+ class AttributionDto(BaseModel):
69
+ """One verdict; the safe value first (the mocked suite fills the first)."""
70
+
71
+ verdict: Literal["attributed", "not_attributed"] = Field(
72
+ description=(
73
+ "ATTRIBUTED: the text gives that party this position — stated in their "
74
+ "own words, reported as what they hold, argue, claim or did, or a fair "
75
+ "condensation of the stance the text gives them (an opponent of X holds "
76
+ "that X is wrong; a rhetorical question that takes a side takes it; a "
77
+ "role and the person in it, or a group and a member named as its voice, "
78
+ "are one party). Wording that goes a little beyond their words while "
79
+ "keeping their side is still theirs. NOT_ATTRIBUTED: the text gives that "
80
+ "party no stance on this at all, gives them the OPPOSITE side, or gives "
81
+ "this position to someone else; a party who is only described, who "
82
+ "disagrees with it, or who is merely asked about it does not hold it."
83
+ )
84
+ )
85
+ reasoning: str = Field(
86
+ description="One sentence: the words that attribute it, or that none do."
87
+ )
88
+
89
+
90
+ class AttributionCheck:
91
+ """Text-only, graph-free, no report: a check the view turn runs."""
92
+
93
+ async def score_texts(
94
+ self, *, position: str, held_by: str, text: str
95
+ ) -> AttributionDto:
96
+ prompt = f"""<text>
97
+ {text}
98
+ </text>
99
+
100
+ Does this text attribute the following position to {held_by.strip()} — stated in their own words, or reported as what they hold, argue, claim or did?
101
+
102
+ "{position.strip()}\""""
103
+ return await ConversationFacilitator().submit(
104
+ response_model=AttributionDto, user_content=prompt
105
+ )
106
+
107
+
108
+ #: The (pole, holder) attribute pairs a tension carries.
109
+ _POLES = (("thesis", "thesis_held_by"), ("antithesis", "antithesis_held_by"))
110
+
111
+
112
+ async def drop_unborne_holders(tensions: Sequence[Any], text: str) -> list[str]:
113
+ """Blank every named holder the text does not bear, IN PLACE, on every
114
+ tension given (the winner and the spares alike — a host serving a
115
+ runner-up must not get the misquotation back). Returns one line per
116
+ holder dropped, for the log. No call where no holder is named; a failed
117
+ judging keeps the holder (fail-soft, see the module)."""
118
+ words = (text or "").strip()
119
+ if not words:
120
+ return []
121
+ jobs: list[tuple[Any, str, str, str]] = []
122
+ for tension in tensions:
123
+ for pole, holder_attr in _POLES:
124
+ holder = (getattr(tension, holder_attr, "") or "").strip()
125
+ position = (getattr(tension, pole, "") or "").strip()
126
+ if holder and position:
127
+ jobs.append((tension, pole, holder_attr, holder))
128
+
129
+ async def one(job: tuple[Any, str, str, str]) -> bool:
130
+ tension, pole, _attr, holder = job
131
+ try:
132
+ dto = await AttributionCheck().score_texts(
133
+ position=getattr(tension, pole), held_by=holder, text=words
134
+ )
135
+ except Exception as exc: # noqa: BLE001 — not judged, the holder stays
136
+ logger.warning("Checking a pole's holder failed; it stays: %s", exc)
137
+ return False
138
+ if not isinstance(dto, AttributionDto):
139
+ return False
140
+ return dto.verdict == "not_attributed"
141
+
142
+ verdicts = await asyncio.gather(*(one(j) for j in jobs))
143
+ dropped: list[str] = []
144
+ for (tension, pole, holder_attr, holder), unborne in zip(jobs, verdicts):
145
+ if unborne:
146
+ setattr(tension, holder_attr, "")
147
+ dropped.append(f"{pole} held_by {holder!r}: the words do not bear it")
148
+ return dropped
@@ -28,6 +28,21 @@ three times". What the handoff found beside it is kept: the ranking reads
28
28
  dialectical validity after the pass floor (`ranking`), and the view turn's
29
29
  judge reads the person's words (`view_sketch.judge_context`).
30
30
 
31
+ The second handoff (2026-10-20, `docs/dev-notes/antithesis-selection.md`, "The hedge wins on coherence")
32
+ found the limit of a coherence-only choice: on a multi-claim input the
33
+ writer drew the claim the person would sign twice in three draws and the
34
+ ranking kept the hedge, because a tetrad about a hedge degenerates more
35
+ coherently than one about a demand, and coherence was all the ranking read.
36
+ So the writers that CHOOSE the thesis (the view turn, the one-shot build)
37
+ ask for a second judge beside the control statements, `ThesisFitCheck`
38
+ (`concerns/thesis_fit_check.py`): is this draw's T the claim they would
39
+ sign, and is its A+ something their words do not already grant. It ranks
40
+ AFTER the pass floor as a yes/no (`SketchVerdict.fits`), so every
41
+ keep-any-passing figure on record stands; it is a selection term, never a
42
+ signal back into generation. The staged writer draws for a GIVEN pair, where
43
+ T and A are the same on every draw, and does not ask. Unmeasured at the time
44
+ of writing — the gates are named on the concern.
45
+
31
46
  What this must NOT become: a loop that optimises against the judge. The judge
32
47
  never sees the thesis or antithesis — it scores the four aspects' coherence
33
48
  and passes a MIRROR antithesis more readily than a genuine position — so a
@@ -48,12 +63,15 @@ holding a text or an object with `.statement` (`TetradDto`'s `AspectDto`).
48
63
  from __future__ import annotations
49
64
 
50
65
  import asyncio
66
+ import dataclasses
51
67
  import logging
52
68
  from dataclasses import dataclass
53
69
  from typing import Any, Optional, Sequence
54
70
 
55
71
  from dialectical_framework.concerns.control_statements_check import \
56
72
  ControlStatementsCheck
73
+ from dialectical_framework.concerns.thesis_fit_check import (
74
+ THESIS_FIT_THRESHOLD, ThesisFitCheck)
57
75
  from dialectical_framework.graph.nodes.estimation import \
58
76
  CONCEPTUAL_COHERENCE_THRESHOLD
59
77
 
@@ -100,6 +118,15 @@ class SketchVerdict:
100
118
  handoff). `None` on the DV fields means the judge did not say — a verdict
101
119
  built from CC alone — and such a verdict ranks BELOW any with a DV, never
102
120
  as a zero (a zero is a claim).
121
+
122
+ `thesis_fit` is the second judge's read (`ThesisFitCheck`, 2026-10-20):
123
+ whether the draw stands on the claim the person would sign, against an
124
+ opposition they have not already granted. `None` here means the fit
125
+ judge did NOT run (the caller did not ask, had no words to read against,
126
+ or the call failed) and the draw ranks as it did before the term existed
127
+ — the opposite convention from DV's, on purpose: DV's `None` is a verdict
128
+ the judge gave without it, fit's `None` is no judging at all, and a
129
+ provider error on one draw must not demote it.
103
130
  """
104
131
 
105
132
  t_plus_without_a_plus: float
@@ -107,6 +134,7 @@ class SketchVerdict:
107
134
  reasoning: str = ""
108
135
  t_plus_without_a_plus_dv: Optional[float] = None
109
136
  a_plus_without_t_plus_dv: Optional[float] = None
137
+ thesis_fit: Optional[float] = None
110
138
 
111
139
  @property
112
140
  def floor(self) -> float:
@@ -130,6 +158,11 @@ class SketchVerdict:
130
158
  def passes(self) -> bool:
131
159
  return self.floor >= CONCEPTUAL_COHERENCE_THRESHOLD
132
160
 
161
+ @property
162
+ def fits(self) -> bool:
163
+ """Not judged, or judged at or above `THESIS_FIT_THRESHOLD`."""
164
+ return self.thesis_fit is None or self.thesis_fit >= THESIS_FIT_THRESHOLD
165
+
133
166
 
134
167
  def aspect_text(value: Any) -> str:
135
168
  """The text of an aspect field: a plain string, or an `AspectDto`'s `.statement`."""
@@ -203,17 +236,61 @@ async def judge_sketches(
203
236
  return list(await asyncio.gather(*(one(c) for c in candidates)))
204
237
 
205
238
 
239
+ async def judge_fit(
240
+ candidates: Sequence[Any], context: str
241
+ ) -> list[Optional[float]]:
242
+ """`ThesisFitCheck` on every candidate, each on its own (gathered), each
243
+ read against the words in `context`. One call per draw beside the
244
+ control statements' two. Fail-soft per candidate: a failed judging is
245
+ `None` — NOT judged, which `SketchVerdict.fits` treats as fitting. A
246
+ candidate without a thesis or antithesis (a transition tetrad through
247
+ `as_control_tetrad`) is not judged either. A draw that names T's holder
248
+ (`thesis_held_by`, the view turn on pasted material) hands it on, so the
249
+ judge asks whose claim about the right party."""
250
+
251
+ async def one(tension: Any) -> Optional[float]:
252
+ thesis = aspect_text(getattr(tension, "thesis", "")).strip()
253
+ antithesis = aspect_text(getattr(tension, "antithesis", "")).strip()
254
+ if not thesis or not antithesis:
255
+ return None
256
+ try:
257
+ verdict = await ThesisFitCheck().score_texts(
258
+ thesis=thesis,
259
+ antithesis=antithesis,
260
+ a_plus=aspect_text(tension.a_plus),
261
+ text=context,
262
+ held_by=(getattr(tension, "thesis_held_by", "") or "").strip(),
263
+ )
264
+ return verdict.fit
265
+ except Exception as exc: # noqa: BLE001 — degrade to "not judged"
266
+ logger.warning("Judging a sketch's thesis fit failed; it ranks unjudged: %s", exc)
267
+ return None
268
+
269
+ return list(await asyncio.gather(*(one(c) for c in candidates)))
270
+
271
+
206
272
  async def select_sketch(
207
- candidates: Sequence[Any], context: str = ""
273
+ candidates: Sequence[Any], context: str = "", fit_against: Optional[str] = None
208
274
  ) -> tuple[int, list[Optional[SketchVerdict]]]:
209
275
  """Judge every candidate (gathered) and return the index of the best plus
210
276
  all verdicts (None where judging failed).
211
277
 
212
278
  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.
279
+ then — with `fit_against` — whether the draw stands on the person's claim
280
+ against an opposition they have not granted (`judge_fit`, a yes/no), then
281
+ the floor's value, dialectical validity, the mean coherence, then the
282
+ earlier draw — a deterministic tie-break, not a preference. A candidate
283
+ whose judging failed sorts last; if every judging failed the first draw
284
+ is kept, which is exactly what `attempts=1` would have done.
285
+
286
+ `fit_against` is the PERSON's words, for the writers that CHOOSE the
287
+ thesis (the view turn's `judge_context`, the one-shot build's material):
288
+ on a given pair every draw has the same T and A and the term would cost
289
+ N calls to say nothing. It is a separate argument from `context` because
290
+ the two judges read different things: the control statements take the
291
+ caller's context (on the build, a paraphrase the Advisor composed), the
292
+ fit judge must read what the person actually said. Empty or `None` skips
293
+ the fit judge — it is never asked blind.
217
294
 
218
295
  ONE candidate is never judged: there is nothing to select among, so
219
296
  `attempts=1` costs no extra call.
@@ -221,19 +298,38 @@ async def select_sketch(
221
298
  if len(candidates) == 1:
222
299
  return 0, [None]
223
300
 
224
- verdicts = await judge_sketches(candidates, context)
301
+ words = (fit_against or "").strip()
302
+ if words:
303
+ verdicts, fits = await asyncio.gather(
304
+ judge_sketches(candidates, context), judge_fit(candidates, words)
305
+ )
306
+ verdicts = [
307
+ None if v is None else dataclasses.replace(v, thesis_fit=f)
308
+ for v, f in zip(verdicts, fits)
309
+ ]
310
+ else:
311
+ verdicts = await judge_sketches(candidates, context)
225
312
  return ranking(verdicts)[0], verdicts
226
313
 
227
314
 
228
315
  def ranking(verdicts: Sequence[Optional[SketchVerdict]]) -> list[int]:
229
- """Candidate indices best first: the weaker control statement, then
316
+ """Candidate indices best first: whether the weaker control statement
317
+ passes, then whether the draw fits the person's claim (`fits`: not
318
+ judged counts as fitting), then the weaker statement's value, then
230
319
  dialectical validity, then the mean coherence, then the earlier draw;
231
320
  unjudged last. `[0]` is the winner; the rest are the runners-up in order.
232
321
 
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:
322
+ Why the pass rule stays FIRST: every best-of-N figure on record (CC 36/40
323
+ on the view turn, 30/40 on the build, 26/46 on documents) is a
324
+ keep-any-passing-draw figure — a term ahead of it would trade those for
325
+ an unmeasured one. The fit term sits INSIDE that rule, as a yes/no: among
326
+ draws that hold, one on the claim the person would sign against an
327
+ opposition they have not granted beats one on a hedge, however coherent
328
+ the hedge (2026-10-20, `docs/dev-notes/antithesis-selection.md`, "The hedge wins on coherence"); among
329
+ draws that fail, the fit decides the same way, so a card with no passing
330
+ draw still shows the person's claim. A yes/no rather than the score
331
+ because the floor's VALUE should keep deciding between two draws that
332
+ both fit, as it did before the term. Why DV comes next, ahead of the mean:
237
333
  DV is the paper's acceptance term beside SP (the Advisor's quality floor
238
334
  already reads it), and it answers the question the choice is about — of
239
335
  two draws that both hold, which is the LIVE tension rather than the
@@ -245,11 +341,19 @@ def ranking(verdicts: Sequence[Optional[SketchVerdict]]) -> list[int]:
245
341
  `explorer._causality_probability`), never as a zero.
246
342
  """
247
343
 
248
- def key(index: int) -> tuple[int, float, float, float, int]:
344
+ def key(index: int) -> tuple[int, bool, bool, float, float, float, int]:
249
345
  verdict = verdicts[index]
250
346
  if verdict is None:
251
- return (0, 0.0, 0.0, 0.0, -index)
347
+ return (0, False, False, 0.0, 0.0, 0.0, -index)
252
348
  dv = verdict.dialectical_validity
253
- return (1, verdict.floor, -1.0 if dv is None else dv, verdict.mean, -index)
349
+ return (
350
+ 1,
351
+ verdict.passes,
352
+ verdict.fits,
353
+ verdict.floor,
354
+ -1.0 if dv is None else dv,
355
+ verdict.mean,
356
+ -index,
357
+ )
254
358
 
255
359
  return sorted(range(len(verdicts)), key=key, reverse=True)
@@ -159,7 +159,14 @@ class TetradSketch(ReasonableConcern[ViewSketchPerspectiveDto], SettingsAware):
159
159
  self._report.summary = f"Sketch failed: {first}"
160
160
  raise first
161
161
 
162
- best, verdicts = await select_sketch(candidates, context or "")
162
+ # This writer chooses the thesis (or words a given one), so the draws
163
+ # are ranked by the person's claim as well as by coherence
164
+ # (`tetrad_candidates.judge_fit`): the fit judge reads the MATERIAL,
165
+ # the person's own words, where the control statements keep the
166
+ # caller's `context`; with no material it is skipped.
167
+ best, verdicts = await select_sketch(
168
+ candidates, context or "", fit_against=material
169
+ )
163
170
  tension = candidates[best]
164
171
  self.verdict = verdicts[best]
165
172
  self.alternatives = [