dialectical-framework 1.6.8__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.6.8 → dialectical_framework-1.7.1}/PKG-INFO +1 -1
  2. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/pyproject.toml +1 -1
  3. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/advisor.py +25 -11
  4. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/system_prompts.py +192 -23
  5. dialectical_framework-1.7.1/src/dialectical_framework/agents/advisor/tools/record_decision.py +85 -0
  6. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/scoped.py +19 -9
  7. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/analyst.py +3 -0
  8. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +4 -0
  9. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +27 -0
  10. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +3 -1
  11. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/system_prompts.py +11 -0
  12. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/app_spec.py +7 -7
  13. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/apps.py +167 -89
  14. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/explorer.py +3 -0
  15. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/system_prompts.py +9 -0
  16. dialectical_framework-1.7.1/src/dialectical_framework/agents/orchestrator/tools/discard.py +23 -0
  17. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +3 -1
  18. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +48 -1
  19. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/aspect_generation.py +45 -0
  20. dialectical_framework-1.7.1/src/dialectical_framework/concerns/decision_coherence_check.py +199 -0
  21. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/dialectical_context.py +88 -10
  22. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/discard.py +31 -2
  23. dialectical_framework-1.7.1/src/dialectical_framework/concerns/record_decision.py +257 -0
  24. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_classification.py +6 -3
  25. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/dialectical_reasoning.py +14 -2
  26. dialectical_framework-1.7.1/src/dialectical_framework/exceptions/node_errors.py +27 -0
  27. dialectical_framework-1.7.1/src/dialectical_framework/graph/nodes/decision.py +151 -0
  28. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationship_manager.py +7 -0
  29. dialectical_framework-1.7.1/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +31 -0
  30. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/rendering.py +59 -0
  31. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/cycle_repository.py +3 -1
  32. dialectical_framework-1.7.1/src/dialectical_framework/graph/repositories/decision_repository.py +81 -0
  33. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/nexus_repository.py +1 -0
  34. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/wheel_repository.py +1 -1
  35. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/scope_context.py +27 -0
  36. dialectical_framework-1.6.8/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -23
  37. dialectical_framework-1.6.8/src/dialectical_framework/exceptions/node_errors.py +0 -10
  38. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/LICENSE +0 -0
  39. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/PATENTS +0 -0
  40. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/README.md +0 -0
  41. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/__init__.py +0 -0
  42. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/__init__.py +0 -0
  43. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  44. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  45. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  46. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  47. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  48. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  49. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  50. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/agent_context.py +0 -0
  51. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  52. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  53. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  54. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  55. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  56. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  57. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  58. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  59. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/execution_report.py +0 -0
  60. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  61. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  62. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  63. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  64. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  65. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  66. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  67. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  68. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  69. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  70. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  71. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  72. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  73. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  74. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  75. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  76. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  77. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  78. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  79. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  80. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/stream_events.py +0 -0
  81. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/agents/toolsets.py +0 -0
  82. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/__init__.py +0 -0
  83. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  84. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  85. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/add_input.py +0 -0
  86. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  87. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  88. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  89. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  90. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  91. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  92. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  93. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  94. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  95. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  96. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  97. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  98. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  99. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  100. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  101. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  102. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  103. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  104. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  105. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  106. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  107. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  108. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  109. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  110. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  111. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  112. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  113. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/source_digest.py +0 -0
  114. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  115. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  116. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  117. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  118. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  119. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  120. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  121. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/__init__.py +0 -0
  122. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/causality_preset.py +0 -0
  123. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/enums/di.py +0 -0
  124. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/events/__init__.py +0 -0
  125. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/events/graph_event.py +0 -0
  126. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  127. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/exceptions/__init__.py +0 -0
  128. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  129. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/__init__.py +0 -0
  130. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  131. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  132. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  133. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  134. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  135. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  136. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  137. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  138. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  139. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  140. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/case.py +0 -0
  141. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  142. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  143. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  144. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/input.py +0 -0
  145. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  146. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  147. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  148. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  149. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  150. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  151. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  152. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  153. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  154. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  155. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  156. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  157. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  158. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  159. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  160. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  161. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  162. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  163. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  164. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  165. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  166. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  167. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  168. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  169. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  170. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  171. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  172. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  173. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  174. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  175. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  176. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  177. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  178. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  179. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  180. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  181. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  182. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  183. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  184. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  185. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  186. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  187. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  188. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  189. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/__init__.py +0 -0
  190. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/has_config.py +0 -0
  191. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  192. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/settings.py +0 -0
  193. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/__init__.py +0 -0
  194. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/bedrock_provider.py +0 -0
  195. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/concurrency.py +0 -0
  196. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/dc_replace.py +0 -0
  197. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  198. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/edge_context.py +0 -0
  199. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/effect_logger.py +0 -0
  200. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/input_context.py +0 -0
  201. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/order_transitions.py +0 -0
  202. {dialectical_framework-1.6.8 → dialectical_framework-1.7.1}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  203. {dialectical_framework-1.6.8 → 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.6.8
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.6.8"
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
@@ -57,18 +58,18 @@ class Advisor:
57
58
 
58
59
  Usage (fresh start):
59
60
  with scope(case.sid):
60
- advisor = Advisor(app_preamble=COUNSELOR_APP)
61
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA)
61
62
  response = await advisor.chat("My son started smoking...")
62
63
 
63
64
  Usage (post-analysis, rich graph exists):
64
65
  with scope(case.sid):
65
66
  context = await DialecticalContext().resolve()
66
- advisor = Advisor(app_preamble=COUNSELOR_APP, dialectical_context=context)
67
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA, dialectical_context=context)
67
68
  response = await advisor.chat("I want to talk through what we found...")
68
69
 
69
70
  Usage (resuming conversation):
70
71
  with scope(case.sid):
71
- advisor = Advisor(app_preamble=COUNSELOR_APP, messages=saved_messages)
72
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA, messages=saved_messages)
72
73
  response = await advisor.chat("What about the other angle?")
73
74
 
74
75
  Usage (app-provided domain tools):
@@ -79,7 +80,7 @@ class Advisor:
79
80
  # rules in the app preamble, where domain vocabulary lives.
80
81
  with scope(case.sid):
81
82
  advisor = Advisor(
82
- app_preamble=ASTRO_COUNSELOR_APP, # explains when to consult the chart
83
+ app_preamble=ASTRO_COUNSELOR_PERSONA, # explains when to consult the chart
83
84
  app_tools=[lookup_natal_chart], # @llm.tool from the app
84
85
  )
85
86
 
@@ -90,7 +91,7 @@ class Advisor:
90
91
  # exploration:
91
92
  with scope(case.sid):
92
93
  advisor = Advisor(
93
- app_preamble=EXPLORATION_ADVISOR_APP,
94
+ app_preamble=NAVIGATOR_APP_EXPLORER_AGENT_COUNSELOR_REGISTER,
94
95
  nexus_hash=explorer.nexus_hash,
95
96
  messages=explorer.messages,
96
97
  )
@@ -120,11 +121,19 @@ 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
127
- # (EXPLORATION_ADVISOR_APP + voicing + tool_guide); standalone uses
136
+ # (NAVIGATOR_APP_EXPLORER_AGENT_COUNSELOR_REGISTER + voicing + tool_guide); standalone uses
128
137
  # the spec's advisor_persona (machinery hidden). See AppSpec.
129
138
  app_preamble, app_tools = resolve_app_layer(
130
139
  app,
@@ -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,11 +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
252
+ from dialectical_framework.agents.advisor.tools.record_decision import \
253
+ build_record_decision
241
254
  from dialectical_framework.agents.advisor.tools.sync import sync
242
255
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
243
256
  inspect_node
@@ -251,6 +264,7 @@ def _build_tools() -> list:
251
264
  anchor,
252
265
  explore,
253
266
  deepen,
267
+ build_record_decision(principal),
254
268
  sync,
255
269
  inspect_node,
256
270
  read_digest,
@@ -258,8 +272,8 @@ def _build_tools() -> list:
258
272
  ]
259
273
 
260
274
 
261
- def _build_scoped_tools(nexus_hash: str) -> list:
275
+ def _build_scoped_tools(nexus_hash: str, principal: str = "human") -> list:
262
276
  from dialectical_framework.agents.advisor.tools.scoped import \
263
277
  build_scoped_tools
264
278
 
265
- return build_scoped_tools(nexus_hash)
279
+ return build_scoped_tools(nexus_hash, principal)
@@ -125,7 +125,7 @@ understanding you've built.
125
125
  The narrow exceptions where no analysis is called for: greetings and small
126
126
  talk, logistics ("can you repeat that?"), pure information requests, and
127
127
  moments of grief or crisis where presence matters more than analysis.
128
-
128
+ {decision_filter_note}
129
129
  Your response to the person never waits on the machinery — speak from what
130
130
  you have. Analysis deepens your next turn; it never delays or deforms this one."""
131
131
 
@@ -255,6 +255,101 @@ You now have the integration vision. Use it to:
255
255
  - Frame synthesis as something they GROW INTO, not something they implement
256
256
  in one move. It emerges from sustained Ac+ and Re+ working together."""
257
257
 
258
+ _DECISION_READINESS = """## Decision Readiness (Convergence)
259
+
260
+ Much of your counsel serves a decision the person must actually make. Mapping
261
+ tensions opens the space; a decision needs the space to CLOSE. These rules
262
+ govern the closing — they apply whenever the conversation is decision-shaped
263
+ (the person is choosing, not just understanding):
264
+
265
+ **The decision bounds the search.** In decision mode, run the search backward
266
+ from the choice: "what would have to be true for this option to be wrong?" —
267
+ not forward from the material. Options are finite; each has a finite set of
268
+ load-bearing assumptions.
269
+
270
+ **Named options (when the choice arrives as "X or Y").** Anchor the pair as
271
+ it comes — the person's own framing is the map they'll recognize. But an
272
+ option-pair is usually a bundle: the options differ along several tensions
273
+ at once, and one tetrad captures one of them. So ask which pull matters most
274
+ before mapping deeper — the person enumerates cheaply what the machinery
275
+ maps expensively — and anchor the same pair again (identical wording) for an
276
+ alternative tetrad only when their reactions show the mapped dimension isn't
277
+ the one the choice turns on. Alternative tetrads are readings of the choice
278
+ itself, not new candidate tensions — the discrimination test below governs
279
+ those. Each perspective names its reading (the dimension its aspects oppose
280
+ along); when a reading strongly resonates — the person keeps returning to
281
+ that dimension — it is itself an anchor candidate: anchor it as its own
282
+ tension (its two poles as thesis and antithesis) to give the root its full
283
+ structure. When weaving, take the reading that resonates plus the tensions
284
+ beyond the fork; sibling readings of one pair only shift the angle. And read
285
+ the anchor result at call time: options that merely differ rather than
286
+ oppose show up as weak opposition (low HS) or a low mode (drifting/absence
287
+ rather than negation) — then the fork is not where the tension is. Keep the
288
+ pair as the person's frame, but also anchor each option alone to discover
289
+ what genuinely opposes it, instead of forcing the menu into a tension it
290
+ doesn't contain.
291
+
292
+ **Discrimination test (when NOT to map).** Before anchoring a new candidate
293
+ tension, ask: if this tension resolved either way, would the person lean
294
+ differently? If not, acknowledge it in conversation but do not map it —
295
+ mapping that cannot change the choice is noise dressed as diligence. But if
296
+ the person wants it mapped anyway, map it: their lived reality outranks the
297
+ test, and what feels load-bearing to them often is.
298
+
299
+ **Saturation.** Surface framings are endless; deep structure is not. When new
300
+ tensions keep landing in already-mapped opposition families (when
301
+ correspondence lines appear in your understanding they are this signal;
302
+ otherwise judge whether the new framing's deep structure matches a mapped
303
+ tension), say so plainly: new framings are collapsing into tensions already
304
+ mapped. Never claim "we found all tensions"; completeness belongs only to
305
+ causal arrangements, which are enumerated systematically for the tensions
306
+ woven in. Tensions saturate; they are never exhausted.
307
+
308
+ **Pre-commit ritual (earned confidence, soft).** Before proposing to record,
309
+ have the choice face its strongest test: name what the unchosen side offers —
310
+ its genuine contribution — and ask whether they can say what choosing costs
311
+ them. Name the trap version of the choice (the resolution that is actually
312
+ one side dominating). A decision that has faced both is earned; record that
313
+ confrontation in the rationale. If the person says "just record it," record
314
+ it — their wish outranks the ritual — and note in the rationale that the cost
315
+ went unconfronted.
316
+
317
+ **Readiness → propose-and-confirm ceremony.** Signals: a leaning is expressed
318
+ and stable, options stopped shifting, recent candidate tensions failed the
319
+ discrimination test. Then — after the ritual above, and only with the
320
+ person's engagement — offer to record the decision. Read the record back in
321
+ their own words: the question, the stance, the why, the accepted cost. Record
322
+ ONLY on their explicit confirmation. A decision is a speech act — it exists
323
+ because they declared it, never because you inferred it. Decisions are NEVER
324
+ recorded silently — whatever else stays behind the scenes, the record is the
325
+ person's artifact, and they must know it exists. Speak of it plainly, in
326
+ their vocabulary — "want me to note this down as your decision, so we can
327
+ hold to it later?" — the record is named in plain words, never as a tool; its
328
+ reference (hash) follows the same disclosure rules as any other node
329
+ reference.
330
+
331
+ **The replacement rule.** Before proposing, check the distilled record
332
+ against the Decisions section of your understanding (and any decisions
333
+ recorded earlier in this conversation): a conflict with a standing decision
334
+ is counsel material, not a silent recording detail — "this would sit against
335
+ what you decided earlier — does it replace that?" If it does, record the new
336
+ decision and, with their consent, retire the old with a reason naming its
337
+ replacement.
338
+
339
+ **After recording — the re-audit.** When a recorded decision exists and a new
340
+ tension or a wobble appears, first ask: does this discriminate against what
341
+ was decided? When it is the already-accepted cost resurfacing, or a framing
342
+ that collapses into what was mapped, reassure FROM the record: they accepted
343
+ this knowingly, here is the recipe they adopted for living with it. That
344
+ reassurance is the record's whole value — but treat the wobble as data
345
+ first, dismissal never: circumstances may genuinely have changed even when
346
+ the wording resembles the accepted cost. If the new tension genuinely
347
+ discriminates — it could change the choice — say so honestly: this reopens
348
+ the decision. Counsel it through, and if a new stance emerges, record it and
349
+ retire the old per the replacement rule above. If a ground of the decision
350
+ has since been discarded, surface that: the record stands, but one of its
351
+ supports moved."""
352
+
258
353
  _TOOLS_INTRO = """## Internal Tools
259
354
 
260
355
  Your internal tools — how you think structurally. Use them eagerly and
@@ -283,8 +378,9 @@ _TOOL_DOCS: dict[str, str] = {
283
378
  "anchor": """- `anchor` — Plants a specific tension from the conversation. More precise
284
379
  than ingest; use when you can see at least the person's position. Two modes:
285
380
  - Thesis + antithesis: you know both sides — creates one polarity and one
286
- perspective (tetrad). Call again with the same T-A for an alternative
287
- tetrad on the same opposition.
381
+ perspective (tetrad). Call again with the same T-A (identical wording —
382
+ a rephrase plants a new tension instead) for an alternative tetrad on
383
+ the same opposition.
288
384
  - Thesis only: anchors their position and discovers what opposes it — finds
289
385
  multiple possible antitheses (each a different polarity), each expanded
290
386
  into a perspective. Richer when you want the framework to reveal
@@ -293,8 +389,9 @@ _TOOL_DOCS: dict[str, str] = {
293
389
  until woven in with `explore`). Use when you can see at least the person's
294
390
  position. Two modes:
295
391
  - Thesis + antithesis: you know both sides — creates one polarity and one
296
- perspective (tetrad). Call again with the same T-A for an alternative
297
- tetrad on the same opposition.
392
+ perspective (tetrad). Call again with the same T-A (identical wording —
393
+ a rephrase plants a new tension instead) for an alternative tetrad on
394
+ the same opposition.
298
395
  - Thesis only: anchors their position and discovers what opposes it — finds
299
396
  multiple possible antitheses (each a different polarity), each expanded
300
397
  into a perspective. Richer when you want the framework to reveal
@@ -352,9 +449,22 @@ _TOOL_DOCS: dict[str, str] = {
352
449
  them. Deepening only adds analytical depth to existing structure — it
353
450
  never changes what the exploration contains, so no consent ceremony is
354
451
  needed. Harmless on already-developed wheels.""",
452
+ "record_decision": """- `record_decision` — Records a decision the person has EXPLICITLY
453
+ confirmed, after the propose-and-confirm ceremony (see Decision Readiness).
454
+ Pass the confirmed wording literally: `question` (what was being decided),
455
+ `stance` (the position taken), `rationale` (the distilled why, including
456
+ reasons outside the mapped structure). Pass `grounds` as {hash, role}
457
+ entries: role "accepted_cost" for the unchosen side's contribution they
458
+ confronted, "adopted_pathway" for the pathway adopted as their ongoing
459
+ recipe, no role for plain grounds (tensions weighed, arrangements counseled
460
+ from). The result may carry a coherence verdict — if it flags a
461
+ contradiction, raise it with the person immediately; the record stands
462
+ either way. To replace or retract a recorded decision, use `discard` on it
463
+ (reason naming the newer decision if one replaces it).""",
355
464
  "sync": """- `sync` — Re-reads the graph state. Without arguments: the full picture —
356
465
  e.g., after multiple ingest/anchor calls, to see all perspectives with
357
- scores before deciding what to group for explore. With a `nexus_hash`:
466
+ scores (and any standing decisions) before deciding what to group for
467
+ explore. With a `nexus_hash`:
358
468
  zooms into that one exploration in full depth — the overview caps wheels
359
469
  per cycle for compactness, the zoom does not. Zoom when counsel settles on
360
470
  one exploration and you need arrangements the overview truncated. NOT
@@ -364,19 +474,24 @@ _TOOL_DOCS: dict[str, str] = {
364
474
  "sync_scoped": """- `sync` — Re-reads this exploration's state. Use when you need a fresh
365
475
  picture after changes. NOT needed at conversation start — the exploration
366
476
  is already in your context.""",
367
- "discard": """- `discard` — Silently retracts something the user rejects. Works on either a
368
- perspective (a whole framing — the tension and its aspects) or a statement
369
- (a single claim). Pass the hash from the anchor/ingest result. Uncommitted
370
- nodes are removed; committed ones are soft-discarded and filtered from future
371
- reasoning. To drop a tension entirely, discard the perspective first, then
372
- its underlying statement if it's no longer wanted (discarding a perspective
373
- leaves its shared statements intact, and a statement still used by a live
374
- perspective won't discard). A perspective already woven into pathways
375
- (cycles/wheels) won't discard — re-anchor the corrected framing instead.""",
477
+ "discard": """- `discard` — Silently retracts something the user rejects. Works on a
478
+ perspective (a whole framing — the tension and its aspects), a statement
479
+ (a single claim), or a recorded decision. Pass the hash from the
480
+ anchor/ingest result. Uncommitted nodes are removed; committed ones are
481
+ soft-discarded and filtered from future reasoning. To drop a tension
482
+ entirely, discard the perspective first, then its underlying statement if
483
+ it's no longer wanted (discarding a perspective leaves its shared
484
+ statements intact, and a statement still used by a live perspective won't
485
+ discard). A perspective already woven into pathways (cycles/wheels) won't
486
+ discard — re-anchor the corrected framing instead. Recorded decisions are
487
+ the exception to silence: retire one only after the person explicitly
488
+ confirms, with a reason naming the replacement if one exists.""",
376
489
  "discard_scoped": """- `discard` — Retracts something the person no longer stands behind, per the
377
490
  consent rules above (confirm for exploration members; a framing you just
378
- anchored needs no ceremony). Works on either a perspective (a whole framing)
379
- or a statement (a single claim). Pass the hash from the anchor result.
491
+ anchored needs no ceremony). Works on a perspective (a whole framing), a
492
+ statement (a single claim), or a recorded decision (always confirmed with
493
+ the person first, with a reason naming the replacement if one exists).
494
+ Pass the hash from the anchor result.
380
495
  Uncommitted nodes are removed; committed ones are soft-discarded and
381
496
  filtered from future reasoning. Members of OTHER explorations refuse.
382
497
  A perspective already woven into pathways (cycles/wheels) won't discard —
@@ -428,7 +543,7 @@ calls for it. It is a compass, not a script:
428
543
  3. Continue conversation, possibly `anchor` again for new tensions
429
544
  4. `explore` → respond with specific paired pathways
430
545
  5. Continue with full depth, offer integration vision when they're ready
431
-
546
+ {decision_arc_step}
432
547
  Counsel grounded in pathways is stronger than counsel from a single tension;
433
548
  counsel from a tension is stronger than counsel from nothing — keep moving the
434
549
  understanding deeper as the conversation allows.
@@ -449,7 +564,7 @@ present findings as structural tables or labeled positions. Do not use
449
564
  framework terminology (thesis, antithesis, polarity, perspective, nexus,
450
565
  wheel, transformation, T+, T-, A+, A-, S+, S-, Ac+, Re+) — unless the app
451
566
  preamble above explicitly grants terminology disclosure, in which case the
452
- preamble's vocabulary rules override this one.
567
+ preamble's vocabulary rules override this one.{decision_speech_note}
453
568
 
454
569
  Speak in the person's own vocabulary about their situation. Statement text
455
570
  from the graph is raw material — rephrase it freely into their language;
@@ -527,6 +642,20 @@ Use `inspect_node` when you want to understand the reasoning behind a score,
527
642
  explain WHY a specific blindspot exists, or ground a pathway recommendation
528
643
  in the actual analytical logic rather than restating the dump.
529
644
 
645
+ **Decisions:** The dump may contain a Decisions section — the person's
646
+ standing decisions, each recorded with their explicit confirmation: the
647
+ question, the stance in their confirmed words, the why, and grounds (the
648
+ tensions, statements, arrangements, and pathways it rests on; "accepted cost" marks the
649
+ unchosen side's contribution they confronted, "adopted pathway" the recipe
650
+ they committed to). Treat these as the person's own declared positions —
651
+ counsel that contradicts a standing decision must name that openly. A
652
+ ground flagged "since discarded" means a support
653
+ moved after the decision — worth surfacing, not silently ignoring. A
654
+ `Validation` line carries the machine coherence verdict: "failed" means a
655
+ flagged contradiction — do not build reassurance on that record; revisit it
656
+ with the person. Absence of an accepted-cost ground means the choice never
657
+ confronted what it gave up — relevant when a wobble arrives.
658
+
530
659
  **Cross-exploration correspondences.** When several explorations coexist,
531
660
  some perspectives carry correspondence lines — machine-stated facts, not
532
661
  suggestions: "Also woven into Nexus [[x]]" (literally the same tension in
@@ -675,6 +804,7 @@ DEFAULT_TOOL_NAMES = [
675
804
  "anchor",
676
805
  "explore",
677
806
  "deepen",
807
+ "record_decision",
678
808
  "sync",
679
809
  "inspect_node",
680
810
  "read_digest",
@@ -709,6 +839,44 @@ def system_prompt(
709
839
  )
710
840
  tool_docs.append(doc)
711
841
 
842
+ decisions_wired = "record_decision" in names
843
+
844
+ def _decision_note(template: str, placeholder: str, note: str) -> str:
845
+ """Render a Decision Readiness cross-reference only when the section
846
+ itself renders (tool wired) — a reference to an absent section would
847
+ dangle. The placeholder sits on its own line: wired → the note set
848
+ off by blank lines, unwired → the line collapses to a paragraph
849
+ break."""
850
+ if placeholder not in template:
851
+ return template
852
+ return template.replace(
853
+ placeholder, f"\n{note}\n" if decisions_wired else ""
854
+ )
855
+
856
+ eager = _decision_note(
857
+ _EAGER_SCOPED if scoped else _EAGER,
858
+ "{decision_filter_note}",
859
+ "In decision-shaped conversations, the Decision Readiness section "
860
+ "below adds one more filter: a candidate tension that could not "
861
+ "change the choice is acknowledged, not mapped.",
862
+ )
863
+ default_arc = _decision_note(
864
+ _DEFAULT_ARC,
865
+ "{decision_arc_step}",
866
+ "6. When the conversation is decision-shaped and readiness signals "
867
+ "appear, close it: the propose-and-confirm ceremony (see Decision "
868
+ "Readiness above), then counsel FROM the record.",
869
+ )
870
+ how_you_speak = _HOW_YOU_SPEAK_SCOPED if scoped else _HOW_YOU_SPEAK
871
+ if "{decision_speech_note}" in how_you_speak:
872
+ speech_note = (
873
+ "\n\nOne exception: the decision record (see Decision Readiness) "
874
+ "is named openly, in plain words — never as a tool or node."
875
+ if decisions_wired
876
+ else ""
877
+ )
878
+ how_you_speak = how_you_speak.replace("{decision_speech_note}", speech_note)
879
+
712
880
  conversation_use = _CONVERSATION_USE
713
881
  if scoped and "ingest" not in names:
714
882
  # Don't reference a tool that isn't wired in this mode.
@@ -720,19 +888,20 @@ def system_prompt(
720
888
  sections = [
721
889
  _ROLE_SCOPED if scoped else _ROLE,
722
890
  _scope_section(scoped_nexus_hash) if scoped else None,
723
- _EAGER_SCOPED if scoped else _EAGER,
891
+ eager,
724
892
  _INTERNAL_MODEL,
725
893
  conversation_use,
894
+ _DECISION_READINESS if decisions_wired else None,
726
895
  _TOOLS_INTRO_SCOPED if scoped else _TOOLS_INTRO,
727
896
  "\n\n".join(tool_docs),
728
897
  _REJECTION_HANDLING_SCOPED if scoped else _REJECTION_HANDLING,
729
- None if scoped else _DEFAULT_ARC,
730
- _HOW_YOU_SPEAK_SCOPED if scoped else _HOW_YOU_SPEAK,
898
+ None if scoped else default_arc,
899
+ how_you_speak,
731
900
  _SCORE_READING,
732
901
  _CONTEXT_SLOT,
733
902
  ]
734
903
  return "\n\n".join(s for s in sections if s)
735
904
 
736
905
 
737
- # Backward-compatible default render (unscoped, all seven tools).
906
+ # Backward-compatible default render (unscoped, all default tools).
738
907
  SYSTEM_PROMPT = system_prompt()
@@ -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()
@@ -18,8 +18,10 @@ the pinned explore weaves them into the exploration, deepen develops an
18
18
  alternative arrangement of the pinned exploration on demand (guarded by
19
19
  wheel-membership), sync/inspect_node/read_digest read, discard retracts
20
20
  (members of the pinned nexus and standalone perspectives — e.g. its own
21
- rejected anchors — but never members of OTHER explorations). Only `ingest`
22
- is excluded — bulk extraction belongs to the unscoped flow.
21
+ rejected anchors — but never members of OTHER explorations), and
22
+ record_decision persists confirmed decisions (Case-level, unguarded by the
23
+ pin). Only `ingest` is excluded — bulk extraction belongs to the unscoped
24
+ flow.
23
25
  """
24
26
 
25
27
  from __future__ import annotations
@@ -30,21 +32,26 @@ from mirascope import llm
30
32
  from pydantic import Field
31
33
 
32
34
 
33
- def build_scoped_tools(nexus_hash: str) -> list:
35
+ def build_scoped_tools(nexus_hash: str, principal: str = "human") -> list:
34
36
  """
35
37
  Build the tool set for an exploration-pinned Advisor (counsel mode).
36
38
 
37
39
  The nexus_hash is captured by closure — the LLM cannot redirect any of
38
40
  these tools to another nexus or create a new one. Validation that the
39
41
  nexus exists happens in Advisor.__init__, not here (keeps the factory
40
- 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.
41
45
  """
42
46
  from dialectical_framework.agents.advisor.tools.anchor import anchor
47
+ from dialectical_framework.agents.advisor.tools.record_decision import \
48
+ build_record_decision
43
49
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
44
50
  inspect_node
45
51
  from dialectical_framework.agents.orchestrator.tools.read_digest import \
46
52
  read_digest
47
53
 
54
+ record_decision = build_record_decision(principal)
48
55
  pinned_hash = nexus_hash
49
56
 
50
57
  @llm.tool
@@ -58,10 +65,10 @@ def build_scoped_tools(nexus_hash: str) -> list:
58
65
 
59
66
  @llm.tool
60
67
  async def discard(
61
- hash: Annotated[str, Field(description="Hash (or prefix) of the Statement or Perspective to discard")],
68
+ hash: Annotated[str, Field(description="Hash (or prefix) of the Statement, Perspective, or Decision to discard")],
62
69
  reason: Annotated[str, Field(description="Why it's being discarded")] = "discarded",
63
70
  ) -> str:
64
- """Mark a Statement or Perspective as discarded when the user disagrees with it or finds it irrelevant. Works on members of this exploration and on standalone perspectives (e.g. a freshly anchored framing the user rejected); refuses members of other explorations. Will refuse if the target participates in existing Cycles/Wheels."""
71
+ """Mark a Statement, Perspective, or Decision as discarded when the user disagrees with it or finds it irrelevant. Works on members of this exploration, on standalone perspectives (e.g. a freshly anchored framing the user rejected), and on recorded decisions the person retracts or replaces; refuses members of other explorations. Will refuse if the target participates in existing Cycles/Wheels."""
65
72
  from dialectical_framework.concerns.discard import Discard
66
73
 
67
74
  refusal = _outside_scope_refusal(pinned_hash, hash)
@@ -107,7 +114,10 @@ def build_scoped_tools(nexus_hash: str) -> list:
107
114
 
108
115
  return await run_deepen(wheel_hash)
109
116
 
110
- return [anchor, sync, inspect_node, read_digest, discard, explore, deepen]
117
+ # record_decision is appended as-is: decisions are Case-level facts, not
118
+ # exploration members — no nexus-scope guard applies (grounds may be
119
+ # exploration members; grounding is read-only w.r.t. the exploration).
120
+ return [anchor, sync, inspect_node, read_digest, discard, explore, deepen, record_decision]
111
121
 
112
122
 
113
123
  def _outside_scope_refusal(nexus_hash: str, target_hash: str) -> str | None:
@@ -132,8 +142,8 @@ def _outside_scope_refusal(nexus_hash: str, target_hash: str) -> str | None:
132
142
 
133
143
  node = NodeRepository().find_by_hash(target_hash)
134
144
  if not isinstance(node, Perspective):
135
- # Not a perspective (likely a statement) — let Discard's own
136
- # guards handle it.
145
+ # Not a perspective (a statement or a decision) — let Discard's own
146
+ # guards handle it; decisions are Case-level, so no nexus pin applies.
137
147
  return None
138
148
  pp = node
139
149
 
@@ -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
@@ -128,6 +128,10 @@ class EditPerspective(ReasonableConcern[EditPerspectiveResult]):
128
128
  # Prepare working PP (clone if committed)
129
129
  if self._was_committed:
130
130
  working_pp = pp.clone()
131
+ # Drop the inherited reading (intent): the axis was named for the
132
+ # ORIGINAL aspects — after a user edit it may no longer describe
133
+ # this tetrad, and a stale reading is worse than none.
134
+ working_pp.intent = None
131
135
  working_pp.save()
132
136
  self._report.node_created(working_pp)
133
137
  else: