dialectical-framework 1.7.0__tar.gz → 1.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/PKG-INFO +1 -1
  2. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/pyproject.toml +1 -1
  3. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/advisor.py +18 -7
  4. dialectical_framework-1.7.1/src/dialectical_framework/agents/advisor/tools/record_decision.py +85 -0
  5. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/scoped.py +6 -3
  6. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/analyst.py +3 -0
  7. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/system_prompts.py +11 -0
  8. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/explorer.py +3 -0
  9. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +1 -1
  10. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +9 -1
  11. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/dialectical_context.py +15 -4
  12. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/record_decision.py +19 -4
  13. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/dialectical_reasoning.py +14 -2
  14. dialectical_framework-1.7.1/src/dialectical_framework/exceptions/node_errors.py +27 -0
  15. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/decision.py +6 -3
  16. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationship_manager.py +7 -0
  17. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/cycle_repository.py +3 -1
  18. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/nexus_repository.py +1 -0
  19. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/wheel_repository.py +1 -1
  20. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/scope_context.py +27 -0
  21. dialectical_framework-1.7.0/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -62
  22. dialectical_framework-1.7.0/src/dialectical_framework/exceptions/node_errors.py +0 -10
  23. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/LICENSE +0 -0
  24. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/PATENTS +0 -0
  25. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/README.md +0 -0
  26. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/__init__.py +0 -0
  27. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/__init__.py +0 -0
  28. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  29. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  30. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  31. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  32. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  33. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  34. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  35. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  36. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/agent_context.py +0 -0
  37. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  38. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  39. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  40. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  41. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  42. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  43. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  44. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  45. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  46. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  47. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/app_spec.py +0 -0
  48. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/apps.py +0 -0
  49. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  50. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/execution_report.py +0 -0
  51. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  52. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  53. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  54. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  55. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  56. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  57. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  58. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  59. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  60. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  61. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  62. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  63. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  64. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  65. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  66. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  67. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  68. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  69. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  70. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  71. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  72. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  73. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/stream_events.py +0 -0
  74. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/toolsets.py +0 -0
  75. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/__init__.py +0 -0
  76. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  77. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  78. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/add_input.py +0 -0
  79. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  80. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  81. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  82. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  83. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  84. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  85. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  86. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  87. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  88. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  89. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  90. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  91. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  92. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  93. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  94. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  95. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  96. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  97. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  98. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  99. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  100. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  101. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  102. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/discard.py +0 -0
  103. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  104. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  105. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  106. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  107. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  108. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  109. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/source_digest.py +0 -0
  110. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  111. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  112. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  113. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  114. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  115. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  116. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  117. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  118. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/__init__.py +0 -0
  119. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/causality_preset.py +0 -0
  120. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/di.py +0 -0
  121. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/events/__init__.py +0 -0
  122. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/events/graph_event.py +0 -0
  123. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  124. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/exceptions/__init__.py +0 -0
  125. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  126. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/__init__.py +0 -0
  127. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  128. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  129. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  130. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  131. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  132. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  133. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  134. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  135. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  136. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  137. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/case.py +0 -0
  138. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  139. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  140. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  141. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/input.py +0 -0
  142. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  143. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  144. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  145. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  146. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  147. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  148. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  149. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  150. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  151. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  152. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  153. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  154. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  155. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  156. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  157. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  158. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  159. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  160. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  161. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  162. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  163. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  164. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  165. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  166. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  167. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  168. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  169. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  170. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  171. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  172. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  173. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  174. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  175. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/rendering.py +0 -0
  176. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  177. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  178. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  179. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  180. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  181. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  182. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  183. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  184. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  185. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  186. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  187. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  188. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  189. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/__init__.py +0 -0
  190. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/has_config.py +0 -0
  191. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  192. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/settings.py +0 -0
  193. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/__init__.py +0 -0
  194. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/bedrock_provider.py +0 -0
  195. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/concurrency.py +0 -0
  196. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/dc_replace.py +0 -0
  197. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  198. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/edge_context.py +0 -0
  199. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/effect_logger.py +0 -0
  200. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/input_context.py +0 -0
  201. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/order_transitions.py +0 -0
  202. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  203. {dialectical_framework-1.7.0 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/use_brain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dialectical-framework
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
5
5
  License: MIT
6
6
  Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "1.7.0"
3
+ version = "1.7.1"
4
4
  description = "A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.)."
5
5
  authors = ["Evaldas Taroza <evaldas@dialexity.com>"]
6
6
  readme = "README.md"
@@ -20,6 +20,7 @@ from pydantic import BaseModel, Field
20
20
  from dialectical_framework.agents.advisor.system_prompts import \
21
21
  system_prompt
22
22
  from dialectical_framework.agents.agent_context import agent_scope
23
+ from dialectical_framework.graph.scope_context import require_current_sid
23
24
  from dialectical_framework.agents.conversation_facilitator import \
24
25
  ConversationFacilitator
25
26
  from dialectical_framework.agents.app_spec import AppSpec, resolve_app_layer
@@ -120,7 +121,15 @@ class Advisor:
120
121
  nexus_hash: Optional[str] = None,
121
122
  app_tools: Optional[list] = None,
122
123
  app: Optional[AppSpec] = None,
124
+ principal: str = "human",
123
125
  ) -> None:
126
+ # principal: WHO confirms decisions in this conversation — a host
127
+ # attestation, fixed for the session (the counterpart doesn't change
128
+ # mid-conversation). "human" = an actual person; a delegated driver
129
+ # (agent-to-agent runs) must pass its own identity ("agent:<name>"
130
+ # or <provider>/<model>) so recorded decisions never claim human
131
+ # confirmation they didn't get. Closed over by record_decision —
132
+ # never an LLM-visible parameter.
124
133
  self._nexus_hash = nexus_hash
125
134
  # app: declarative app definition — composition depends on the mode:
126
135
  # counsel toggle (nexus_hash set) keeps the Navigator contract
@@ -134,9 +143,9 @@ class Advisor:
134
143
  )
135
144
  if nexus_hash:
136
145
  self._validate_nexus(nexus_hash)
137
- self._tools = _build_scoped_tools(nexus_hash)
146
+ self._tools = _build_scoped_tools(nexus_hash, principal)
138
147
  else:
139
- self._tools = _build_tools()
148
+ self._tools = _build_tools(principal)
140
149
  # App-provided @llm.tool functions (domain resources: chart lookups,
141
150
  # methodology references, ...) — see toolsets.merge_app_tools.
142
151
  self._tools = merge_app_tools(self._tools, app_tools)
@@ -185,12 +194,14 @@ class Advisor:
185
194
  return "\n\n".join(parts)
186
195
 
187
196
  async def chat(self, user_message: str) -> str:
197
+ require_current_sid() # unscoped turns silently drop all work
188
198
  with agent_scope(self.AGENT_NAME):
189
199
  await self._render_pending_context()
190
200
  result = await self._conversation.submit(ChatResponse, user_message)
191
201
  return result.message
192
202
 
193
203
  async def chat_stream(self, user_message: str) -> AsyncGenerator[StreamEvent, None]:
204
+ require_current_sid() # unscoped turns silently drop all work
194
205
  with agent_scope(self.AGENT_NAME):
195
206
  await self._render_pending_context()
196
207
  async for event in self._conversation.submit_stream(
@@ -233,13 +244,13 @@ class Advisor:
233
244
  return self._conversation._messages
234
245
 
235
246
 
236
- def _build_tools() -> list:
247
+ def _build_tools(principal: str = "human") -> list:
237
248
  from dialectical_framework.agents.advisor.tools.anchor import anchor
238
249
  from dialectical_framework.agents.advisor.tools.deepen import deepen
239
250
  from dialectical_framework.agents.advisor.tools.explore import explore
240
251
  from dialectical_framework.agents.advisor.tools.ingest import ingest
241
252
  from dialectical_framework.agents.advisor.tools.record_decision import \
242
- record_decision
253
+ build_record_decision
243
254
  from dialectical_framework.agents.advisor.tools.sync import sync
244
255
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
245
256
  inspect_node
@@ -253,7 +264,7 @@ def _build_tools() -> list:
253
264
  anchor,
254
265
  explore,
255
266
  deepen,
256
- record_decision,
267
+ build_record_decision(principal),
257
268
  sync,
258
269
  inspect_node,
259
270
  read_digest,
@@ -261,8 +272,8 @@ def _build_tools() -> list:
261
272
  ]
262
273
 
263
274
 
264
- def _build_scoped_tools(nexus_hash: str) -> list:
275
+ def _build_scoped_tools(nexus_hash: str, principal: str = "human") -> list:
265
276
  from dialectical_framework.agents.advisor.tools.scoped import \
266
277
  build_scoped_tools
267
278
 
268
- return build_scoped_tools(nexus_hash)
279
+ return build_scoped_tools(nexus_hash, principal)
@@ -0,0 +1,85 @@
1
+ """
2
+ record_decision tool: persist a decision the person explicitly confirmed.
3
+
4
+ The ceremony lives in conversation (propose → read back → confirm) — this
5
+ tool is called only AFTER the person's explicit yes, with the confirmed
6
+ wording passed literally. Retracting or replacing a recorded decision goes
7
+ through the standard `discard` tool (reason referencing the new decision).
8
+
9
+ Provenance: WHO confirmed is a host-attested fact, closed over at tool
10
+ construction (Advisor(principal=...)) — never an LLM-supplied parameter.
11
+ "human" (the default) means an actual person confirmed the wording; a
12
+ delegated driver (agent-to-agent runs) must be constructed with its own
13
+ identity, else the ledger and inspect_node would present machine-authored
14
+ rationales as the person's own confirmed "why".
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Annotated
20
+
21
+ from mirascope import llm
22
+ from pydantic import Field
23
+
24
+ from dialectical_framework.concerns.record_decision import GroundLink
25
+
26
+
27
+ def build_record_decision(principal: str = "human"):
28
+ """Build the record_decision tool with the confirming principal
29
+ closed over (same code-not-prompt enforcement as the nexus pin in
30
+ scoped.py)."""
31
+
32
+ attested_principal = principal
33
+
34
+ @llm.tool
35
+ async def record_decision(
36
+ question: Annotated[
37
+ str,
38
+ Field(description="The question that was being decided, in the person's confirmed words"),
39
+ ],
40
+ stance: Annotated[
41
+ str,
42
+ Field(description="The stance the person committed to, in their confirmed words"),
43
+ ],
44
+ rationale: Annotated[
45
+ str,
46
+ Field(
47
+ description="The distilled why behind the decision as the person confirmed it, "
48
+ "including reasons that live outside the mapped structure (deadlines, "
49
+ "people, external facts)"
50
+ ),
51
+ ],
52
+ grounds: Annotated[
53
+ list[GroundLink] | None,
54
+ Field(
55
+ description="Nodes this decision rests on: {hash, role?} each. "
56
+ "role='accepted_cost' for the unchosen side's contribution the person "
57
+ "confronted and accepted; role='adopted_pathway' for the pathway adopted "
58
+ "as the ongoing recipe; omit role for a plain ground (tensions weighed, "
59
+ "arrangements counseled from)."
60
+ ),
61
+ ] = None,
62
+ ) -> str:
63
+ """Record a decision the person has EXPLICITLY confirmed in conversation. Never call this silently or speculatively — propose the distilled record first, read it back, and record only on their clear yes. Links the decision to the graph nodes that grounded it and attaches the confirmed rationale. To replace or retract a recorded decision, use `discard` on it (with a reason naming the newer decision if one replaces it)."""
64
+ from dialectical_framework.concerns.record_decision import RecordDecision
65
+
66
+ # Mirascope passes json.loads'd kwargs without coercing nested models, so
67
+ # `grounds` arrives as raw dicts — RecordDecision.resolve normalizes them
68
+ # (single owner of that normalization) and refuses in-band on bad input.
69
+ concern = RecordDecision()
70
+ await concern.resolve(
71
+ question=question,
72
+ stance=stance,
73
+ rationale=rationale,
74
+ grounds=grounds,
75
+ principal=attested_principal,
76
+ )
77
+ return str(concern.report)
78
+
79
+ return record_decision
80
+
81
+
82
+ # Default build: a human principal. Kept module-level so existing imports
83
+ # (toolset builders, tests) stay valid; hosts with a non-human driver pass
84
+ # principal= at Advisor construction instead of importing this.
85
+ record_decision = build_record_decision()
@@ -32,23 +32,26 @@ from mirascope import llm
32
32
  from pydantic import Field
33
33
 
34
34
 
35
- def build_scoped_tools(nexus_hash: str) -> list:
35
+ def build_scoped_tools(nexus_hash: str, principal: str = "human") -> list:
36
36
  """
37
37
  Build the tool set for an exploration-pinned Advisor (counsel mode).
38
38
 
39
39
  The nexus_hash is captured by closure — the LLM cannot redirect any of
40
40
  these tools to another nexus or create a new one. Validation that the
41
41
  nexus exists happens in Advisor.__init__, not here (keeps the factory
42
- DB-free for signature tests).
42
+ DB-free for signature tests). `principal` (host-attested confirmer
43
+ identity, closed over the same way) reaches record_decision — see
44
+ tools/record_decision.py.
43
45
  """
44
46
  from dialectical_framework.agents.advisor.tools.anchor import anchor
45
47
  from dialectical_framework.agents.advisor.tools.record_decision import \
46
- record_decision
48
+ build_record_decision
47
49
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
48
50
  inspect_node
49
51
  from dialectical_framework.agents.orchestrator.tools.read_digest import \
50
52
  read_digest
51
53
 
54
+ record_decision = build_record_decision(principal)
52
55
  pinned_hash = nexus_hash
53
56
 
54
57
  @llm.tool
@@ -16,6 +16,7 @@ from mirascope import llm
16
16
  from pydantic import BaseModel, Field
17
17
 
18
18
  from dialectical_framework.agents.agent_context import agent_scope
19
+ from dialectical_framework.graph.scope_context import require_current_sid
19
20
  from dialectical_framework.agents.analyst.system_prompts import SYSTEM_PROMPT
20
21
  from dialectical_framework.agents.conversation_facilitator import \
21
22
  ConversationFacilitator
@@ -93,11 +94,13 @@ class Analyst:
93
94
  return "\n\n".join(parts)
94
95
 
95
96
  async def chat(self, user_message: str) -> str:
97
+ require_current_sid() # unscoped turns silently drop all work
96
98
  with agent_scope(self.AGENT_NAME):
97
99
  result = await self._conversation.submit(ChatResponse, user_message)
98
100
  return result.message
99
101
 
100
102
  async def chat_stream(self, user_message: str) -> AsyncGenerator[StreamEvent, None]:
103
+ require_current_sid() # unscoped turns silently drop all work
101
104
  with agent_scope(self.AGENT_NAME):
102
105
  async for event in self._conversation.submit_stream(
103
106
  ChatResponse, user_message
@@ -67,6 +67,17 @@ Always check resonance AFTER presenting results — but never before acting.
67
67
  When new tensions emerge from conversation:
68
68
  - Call `analyze` with `thesis_hashes` to develop them without re-processing everything.
69
69
 
70
+ **When the user declares a decision** ("that settles it, I'm going with X")
71
+ or asks to have a decision noted or recorded: you cannot record decisions —
72
+ that happens in the advisory conversation, not here. NEVER claim to have
73
+ noted a decision yourself, and NEVER anchor the declared choice as a thesis —
74
+ a decision is a stance the user takes, not a tension to analyze; persisting
75
+ it as a statement would misfile it in the analytical structure. Acknowledge
76
+ their choice plainly and continue the structural work; if the application
77
+ offers an advisory conversation, point them there for recording. Decisions
78
+ already recorded are readable: they appear via `query_graph` (Decision
79
+ nodes) and `inspect_node` by hash.
80
+
70
81
  **Inputs from exploration (`dx://` URIs) — the round-trip.** An input whose
71
82
  content starts with `dx://` is an exploration insight fed back for analysis:
72
83
  it references a Transition (an action/reflection pathway) whose wisdom
@@ -16,6 +16,7 @@ from mirascope import llm
16
16
  from pydantic import BaseModel, Field
17
17
 
18
18
  from dialectical_framework.agents.agent_context import agent_scope
19
+ from dialectical_framework.graph.scope_context import require_current_sid
19
20
  from dialectical_framework.agents.conversation_facilitator import \
20
21
  ConversationFacilitator
21
22
  from dialectical_framework.agents.explorer.system_prompts import system_prompt
@@ -109,11 +110,13 @@ class Explorer:
109
110
  return "\n\n".join(parts)
110
111
 
111
112
  async def chat(self, user_message: str) -> str:
113
+ require_current_sid() # unscoped turns silently drop all work
112
114
  with agent_scope(self.AGENT_NAME):
113
115
  result = await self._conversation.submit(ChatResponse, user_message)
114
116
  return result.message
115
117
 
116
118
  async def chat_stream(self, user_message: str) -> AsyncGenerator[StreamEvent, None]:
119
+ require_current_sid() # unscoped turns silently drop all work
117
120
  with agent_scope(self.AGENT_NAME):
118
121
  async for event in self._conversation.submit_stream(
119
122
  ChatResponse, user_message
@@ -31,7 +31,7 @@ GRAPH_SCHEMA = """## Graph Schema
31
31
  | Transformation | Action-reflection structure (Ac, Re, Ac+, Ac-, Re+, Re-) on a Wheel edge | `intent` |
32
32
  | Transition | Movement between two Statements (source → target) | `statement`, `headline`, `haiku` |
33
33
  | Synthesis | Emergent S+/S- pair from a Wheel's circular causality | |
34
- | Decision | User-confirmed decision: question + stance (Case-level, immutable; timestamp = `committed_at`; the why = attached Rationale with `agent`="human") | `intent` (the question), `stance`, `discarded`, `validation` ("passed" / "failed: ..." / null=unchecked) |
34
+ | Decision | Confirmed decision: question + stance (Case-level, immutable; timestamp = `committed_at`; the why = attached Rationale whose `agent` names the confirming principal — "human" for a person, "agent:<name>" for a delegated driver) | `intent` (the question), `stance`, `discarded`, `validation` ("passed" / "failed: ..." / null=unchecked) |
35
35
  | Rationale | Explanation attached to any node | `text` |
36
36
  | Estimation | Numeric assessment (probability, relevance, feasibility) | `value` |
37
37
 
@@ -288,7 +288,15 @@ def _inspect_decision(decision: Decision) -> str:
288
288
  lines.append(f"Stance: {decision.stance}")
289
289
 
290
290
  for rationale, _ in decision.rationales.all():
291
- prefix = "Why (human)" if rationale.agent == "human" else f"Rationale ({rationale.agent})"
291
+ # "human" = a person confirmed the ceremony; "agent:<name>" = a
292
+ # delegated driver confirmed (still the ceremony why, attributed);
293
+ # anything else = machine-generated analysis (critique etc.).
294
+ if rationale.agent == "human":
295
+ prefix = "Why (human)"
296
+ elif rationale.agent and rationale.agent.startswith("agent:"):
297
+ prefix = f"Why (confirmed by {rationale.agent})"
298
+ else:
299
+ prefix = f"Rationale ({rationale.agent})"
292
300
  lines.append(f"{prefix}: {rationale.text}")
293
301
 
294
302
  grounds = decision.grounds.all()
@@ -252,12 +252,20 @@ class DialecticalContext(ReasonableConcern[str], SettingsAware):
252
252
  # raw they could fabricate sibling ledger lines (injection).
253
253
  lines.append(f"Question: {one_line(d.intent)}")
254
254
  lines.append(f"Stance: {one_line(d.stance)}")
255
- # Ledger shows only the human-confirmed why; machine rationales
255
+ # Ledger shows only the ceremony-confirmed why; machine rationales
256
256
  # (critiques etc.) stay one inspect_node away — deliberate
257
- # divergence from _inspect_decision, which shows all.
257
+ # divergence from _inspect_decision, which shows all. "human" =
258
+ # a person confirmed; "agent:<name>" = a delegated driver
259
+ # confirmed (rendered with attribution so a human reading the
260
+ # ledger never mistakes it for their own confirmation).
258
261
  for rationale, _ in d.rationales.all():
259
262
  if rationale.agent == "human":
260
263
  lines.append(f"Why: {one_line(rationale.text)}")
264
+ elif rationale.agent and rationale.agent.startswith("agent:"):
265
+ lines.append(
266
+ f"Why (confirmed by {rationale.agent}): "
267
+ f"{one_line(rationale.text)}"
268
+ )
261
269
  if d.validation:
262
270
  lines.append(f"Validation: {one_line(d.validation)}")
263
271
  grounds = d.grounds.all()
@@ -514,10 +522,13 @@ class DialecticalContext(ReasonableConcern[str], SettingsAware):
514
522
  total_wheel_prob = sum(p for p in wheel_probs.values() if p is not None)
515
523
 
516
524
  max_wheels = 0 if self._nexus_hash else self.settings.advisor_wheel_quality_top_plausible
525
+ # Hash tiebreaker: with probability-only keys, tied (or unscored)
526
+ # wheels resolve by arrival order — unspecified in Cypher — so
527
+ # which wheel makes the rendered top-N could differ between two
528
+ # renders of the SAME graph. The hash pins it.
517
529
  rendered = sorted(
518
530
  wheels,
519
- key=lambda w: wheel_probs.get(w._id) or -1.0,
520
- reverse=True,
531
+ key=lambda w: (-(wheel_probs.get(w._id) or -1.0), w.hash or ""),
521
532
  )
522
533
  hidden = 0
523
534
  if max_wheels > 0 and len(rendered) > max_wheels:
@@ -9,7 +9,9 @@ confirmed wording, never as instructions to interpret. The concern:
9
9
  fail-closed for grounds, since a dangling or reader-invisible ground
10
10
  would break the record's value)
11
11
  2. Commits the Decision (question rides intent, stance frozen in hash)
12
- 3. Attaches the distilled why as a Rationale with agent="human"
12
+ 3. Attaches the distilled why as a Rationale carrying the confirming
13
+ principal's provenance (agent="human" only when a person confirmed;
14
+ delegated drivers pass their own identity — see resolve())
13
15
  4. Connects GROUNDED_IN edges with their roles
14
16
  5. Runs DecisionCoherenceCheck and flags the verdict (fail-soft: a failed
15
17
  or errored check NEVER blocks the record — soft gate)
@@ -62,7 +64,19 @@ class RecordDecision(ReasonableConcern[str | None]):
62
64
  stance: str,
63
65
  rationale: str,
64
66
  grounds: list[GroundLink] | None = None,
67
+ principal: str = "human",
65
68
  ) -> str | None:
69
+ """
70
+ `principal` is the provenance stamped on the decision's Rationale —
71
+ WHO confirmed the recording ceremony. "human" (default) is the
72
+ sentinel meaning an actual person confirmed the wording; a delegated
73
+ driver (agent-to-agent runs) must pass its own identity (e.g.
74
+ "agent:<name>" or a <provider>/<model> string) — the ledger and
75
+ inspect_node render only human-confirmed rationales as the person's
76
+ own "why", so a false "human" here corrupts the record's authority
77
+ semantics unfixably. Attested by the HOST at agent construction
78
+ (Advisor(principal=...)), never by the LLM.
79
+ """
66
80
  from dialectical_framework.concerns.decision_coherence_check import \
67
81
  DecisionCoherenceCheck
68
82
  from dialectical_framework.graph.nodes.assessable_entity import \
@@ -170,12 +184,13 @@ class RecordDecision(ReasonableConcern[str | None]):
170
184
  # a raw exception hiding that a Decision now exists.
171
185
  attach_failures: list[str] = []
172
186
  try:
173
- # The distilled why: a human-provenance Rationale.
187
+ # The distilled why: a Rationale carrying the confirming
188
+ # principal's provenance (see resolve() docstring).
174
189
  why = Rationale(text=rationale)
175
- why.agent = "human" # overwrite the auto-filled model identifier
190
+ why.agent = principal # overwrite the auto-filled model identifier
176
191
  why.set_explanation_target(decision)
177
192
  why.commit() # auto-connects EXPLAINS
178
- self._report.node_created(why, patch={"agent": "human"})
193
+ self._report.node_created(why, patch={"agent": principal})
179
194
  self._report.relationship_created(why.explains, why, decision)
180
195
  except Exception as e:
181
196
  attach_failures.append(f"rationale not attached ({e})")
@@ -166,8 +166,20 @@ class DialecticalReasoning(containers.DeclarativeContainer):
166
166
  graph_db.execute(
167
167
  "CREATE CONSTRAINT ON (n:Node) ASSERT n.hash, n.sid IS UNIQUE"
168
168
  )
169
- except Exception:
170
- pass # Constraint may already exist or DB doesn't support it
169
+ except Exception as e:
170
+ # Constraint may already exist or DB doesn't support it —
171
+ # but say so: this constraint is the only backstop against
172
+ # commit()'s check-then-act dedup race producing duplicate
173
+ # committed nodes under concurrent writers. Silent absence
174
+ # turns that race into silent corruption.
175
+ import logging
176
+
177
+ logging.getLogger(__name__).warning(
178
+ "Could not create (hash, sid) uniqueness constraint: %s — "
179
+ "duplicate-node protection is NOT active; ensure a single "
180
+ "writer per sid.",
181
+ e,
182
+ )
171
183
 
172
184
  # Graph database (Memgraph or Neo4j) for graph-native dialectical structures
173
185
  graph_db: providers.Singleton[Union[Memgraph, Neo4j]] = providers.Singleton(
@@ -0,0 +1,27 @@
1
+ """
2
+ Node-related exceptions for the dialectical framework.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+
8
+ class ImmutableNodeError(Exception):
9
+ """Raised when attempting to modify a committed (immutable) node's structure."""
10
+
11
+
12
+ class MissingScopeError(RuntimeError):
13
+ """Raised when an agent turn starts with no scope (sid) set.
14
+
15
+ Agent conversations (Analyst/Explorer/Advisor chat) write sid-scoped
16
+ nodes through their tools. Without `with scope(case.sid):` the turn
17
+ would fail SILENTLY: nodes save with sid=None, invisible to every
18
+ repository listing (work dropped while tool reports claim success),
19
+ and commit-time dedup falls back to an UNSCOPED find_by_hash that can
20
+ alias a node onto another Case's node, attaching foreign edges to it.
21
+
22
+ Deliberately an agent-level guard, not a BaseNode one: sid-less
23
+ "orphan" nodes are a supported node-layer semantic (portable
24
+ identifiers — see TestOrphanNodes), so programmatic callers may
25
+ create them; conversational turns never legitimately run unscoped.
26
+ """
27
+
@@ -32,9 +32,12 @@ class Decision(IntentMixin, AssessableEntity, label="Decision"):
32
32
  Thin by design — the record points at the reasoning rather than
33
33
  restating it:
34
34
  - The question rides `intent` (IntentMixin); REQUIRED at commit.
35
- - The distilled why is a Rationale node with agent="human" attached
36
- via the standard EXPLAINS machinery (inherited `rationales`), which
37
- also buys ratings/estimations/critiques on decisions.
35
+ - The distilled why is a Rationale attached via the standard EXPLAINS
36
+ machinery (inherited `rationales`), which also buys ratings/
37
+ estimations/critiques on decisions. Its `agent` names the confirming
38
+ principal: "human" iff a person confirmed the ceremony; a delegated
39
+ driver records "agent:<name>" (host-attested via
40
+ Advisor(principal=...), never LLM-supplied).
38
41
  - Grounds are GROUNDED_IN edges to committed nodes (perspectives,
39
42
  statements, wheels, transformations) with an optional role
40
43
  ("accepted_cost", "adopted_pathway", or None for a plain ground).
@@ -1162,10 +1162,17 @@ class BoundRelationshipManager(Generic[T]):
1162
1162
  else: # any
1163
1163
  pattern = f"(source)-[r:{self.relationship_type}]-(target:{resolved_labels})"
1164
1164
 
1165
+ # Deterministic ordering: without ORDER BY, Cypher result order is
1166
+ # unspecified (stable only by accident of current storage) — but
1167
+ # consumers like build_pp_index treat this ordering as canonical
1168
+ # (T1/T2 indices, cycle sequences). committed_at gives temporal
1169
+ # order; id(target) tiebreaks same-second commits and uncommitted
1170
+ # targets (NULL committed_at sorts consistently within a vendor).
1165
1171
  query = f"""
1166
1172
  MATCH {pattern}
1167
1173
  WHERE id(source) = $source_id
1168
1174
  RETURN target, r as relationship
1175
+ ORDER BY target.committed_at ASC, id(target) ASC
1169
1176
  """
1170
1177
 
1171
1178
  results = db.execute_and_fetch(query, {"source_id": self.source_node._id})
@@ -83,6 +83,7 @@ class CycleRepository:
83
83
  AND size(c.perspective_hashes) = $hash_count
84
84
  AND ALL(h IN $pp_hashes WHERE h IN c.perspective_hashes)
85
85
  RETURN c
86
+ ORDER BY c.committed_at ASC, id(c) ASC
86
87
  """
87
88
  results = list(graph_db.execute_and_fetch(query, {
88
89
  "sid": sid,
@@ -107,6 +108,7 @@ class CycleRepository:
107
108
  WHERE c.sid = $sid
108
109
  AND ANY(h IN $pp_hashes WHERE h IN c.perspective_hashes)
109
110
  RETURN c
111
+ ORDER BY c.committed_at ASC, id(c) ASC
110
112
  """
111
113
  results = list(graph_db.execute_and_fetch(query, {
112
114
  "sid": sid,
@@ -165,7 +167,7 @@ class CycleRepository:
165
167
  query += " AND ALL(h IN c.perspective_hashes WHERE h IN $nexus_pp_hashes)\n"
166
168
  params["nexus_pp_hashes"] = nexus_pp_hashes
167
169
 
168
- query += " RETURN c"
170
+ query += " RETURN c\n ORDER BY c.committed_at ASC, id(c) ASC"
169
171
 
170
172
  results = list(graph_db.execute_and_fetch(query, params))
171
173
 
@@ -82,6 +82,7 @@ class NexusRepository:
82
82
  query = """
83
83
  MATCH (n:Nexus {sid: $sid})
84
84
  RETURN n
85
+ ORDER BY n.committed_at ASC, id(n) ASC
85
86
  """
86
87
  results = list(graph_db.execute_and_fetch(query, {"sid": sid}))
87
88
  return [r["n"] for r in results]
@@ -197,7 +197,7 @@ class WheelRepository:
197
197
  query += " AND ALL(h IN c.perspective_hashes WHERE h IN $nexus_pp_hashes)\n"
198
198
  params["nexus_pp_hashes"] = nexus_pp_hashes
199
199
 
200
- query += " RETURN w"
200
+ query += " RETURN w\n ORDER BY w.committed_at ASC, id(w) ASC"
201
201
 
202
202
  results = list(graph_db.execute_and_fetch(query, params))
203
203
 
@@ -33,6 +33,33 @@ _current_scope: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
33
33
  )
34
34
 
35
35
 
36
+ def require_current_sid() -> str:
37
+ """
38
+ Get the current scope ID (sid), raising if none is set.
39
+
40
+ Guard for entry points that must never run unscoped (agent chat
41
+ turns): running without scope fails silently otherwise — nodes save
42
+ with sid=None (invisible to every sid-scoped listing) and commit-time
43
+ dedup falls back to an unscoped lookup that can alias onto another
44
+ Case's nodes. Fail loud at the boundary instead.
45
+
46
+ Note: this is NOT enforced at the node layer — sid-less orphan nodes
47
+ are a supported semantic for programmatic callers (portable
48
+ identifiers). Only conversational entry points call this.
49
+ """
50
+ from dialectical_framework.exceptions.node_errors import \
51
+ MissingScopeError
52
+
53
+ sid = _current_scope.get()
54
+ if not sid:
55
+ raise MissingScopeError(
56
+ "No scope set for this agent turn. Wrap the call in "
57
+ "`with scope(case.sid):` — the framework never sets scope "
58
+ "itself, and running unscoped would silently drop all work."
59
+ )
60
+ return sid
61
+
62
+
36
63
  def get_current_sid() -> Optional[str]:
37
64
  """
38
65
  Get the current scope ID (sid) from context.
@@ -1,62 +0,0 @@
1
- """
2
- record_decision tool: persist a decision the person explicitly confirmed.
3
-
4
- The ceremony lives in conversation (propose → read back → confirm) — this
5
- tool is called only AFTER the person's explicit yes, with the confirmed
6
- wording passed literally. Retracting or replacing a recorded decision goes
7
- through the standard `discard` tool (reason referencing the new decision).
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- from typing import Annotated
13
-
14
- from mirascope import llm
15
- from pydantic import Field
16
-
17
- from dialectical_framework.concerns.record_decision import GroundLink
18
-
19
-
20
- @llm.tool
21
- async def record_decision(
22
- question: Annotated[
23
- str,
24
- Field(description="The question that was being decided, in the person's confirmed words"),
25
- ],
26
- stance: Annotated[
27
- str,
28
- Field(description="The stance the person committed to, in their confirmed words"),
29
- ],
30
- rationale: Annotated[
31
- str,
32
- Field(
33
- description="The distilled why behind the decision as the person confirmed it, "
34
- "including reasons that live outside the mapped structure (deadlines, "
35
- "people, external facts)"
36
- ),
37
- ],
38
- grounds: Annotated[
39
- list[GroundLink] | None,
40
- Field(
41
- description="Nodes this decision rests on: {hash, role?} each. "
42
- "role='accepted_cost' for the unchosen side's contribution the person "
43
- "confronted and accepted; role='adopted_pathway' for the pathway adopted "
44
- "as the ongoing recipe; omit role for a plain ground (tensions weighed, "
45
- "arrangements counseled from)."
46
- ),
47
- ] = None,
48
- ) -> str:
49
- """Record a decision the person has EXPLICITLY confirmed in conversation. Never call this silently or speculatively — propose the distilled record first, read it back, and record only on their clear yes. Links the decision to the graph nodes that grounded it and attaches the confirmed rationale. To replace or retract a recorded decision, use `discard` on it (with a reason naming the newer decision if one replaces it)."""
50
- from dialectical_framework.concerns.record_decision import RecordDecision
51
-
52
- # Mirascope passes json.loads'd kwargs without coercing nested models, so
53
- # `grounds` arrives as raw dicts — RecordDecision.resolve normalizes them
54
- # (single owner of that normalization) and refuses in-band on bad input.
55
- concern = RecordDecision()
56
- await concern.resolve(
57
- question=question,
58
- stance=stance,
59
- rationale=rationale,
60
- grounds=grounds,
61
- )
62
- return str(concern.report)
@@ -1,10 +0,0 @@
1
- """
2
- Node-related exceptions for the dialectical framework.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
-
8
- class ImmutableNodeError(Exception):
9
- """Raised when attempting to modify a committed (immutable) node's structure."""
10
-