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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/PKG-INFO +1 -1
  2. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/pyproject.toml +1 -1
  3. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/advisor.py +36 -4
  4. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/system_prompts.py +192 -23
  5. dialectical_framework-1.7.0/src/dialectical_framework/agents/advisor/tools/record_decision.py +62 -0
  6. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/scoped.py +14 -7
  7. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/analyst.py +17 -2
  8. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +4 -0
  9. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +27 -0
  10. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +3 -1
  11. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/system_prompts.py +10 -0
  12. dialectical_framework-1.7.0/src/dialectical_framework/agents/app_spec.py +119 -0
  13. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/apps.py +173 -89
  14. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/explorer.py +15 -1
  15. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/system_prompts.py +9 -0
  16. dialectical_framework-1.7.0/src/dialectical_framework/agents/orchestrator/tools/discard.py +23 -0
  17. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +3 -1
  18. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +40 -1
  19. dialectical_framework-1.7.0/src/dialectical_framework/agents/toolsets.py +33 -0
  20. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/aspect_generation.py +45 -0
  21. dialectical_framework-1.7.0/src/dialectical_framework/concerns/decision_coherence_check.py +199 -0
  22. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/dialectical_context.py +75 -8
  23. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/discard.py +31 -2
  24. dialectical_framework-1.7.0/src/dialectical_framework/concerns/record_decision.py +242 -0
  25. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/statement_classification.py +6 -3
  26. dialectical_framework-1.7.0/src/dialectical_framework/graph/nodes/decision.py +148 -0
  27. dialectical_framework-1.7.0/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +31 -0
  28. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/rendering.py +59 -0
  29. dialectical_framework-1.7.0/src/dialectical_framework/graph/repositories/decision_repository.py +81 -0
  30. dialectical_framework-1.6.7/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -23
  31. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/LICENSE +0 -0
  32. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/PATENTS +0 -0
  33. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/README.md +0 -0
  34. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/__init__.py +0 -0
  35. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/__init__.py +0 -0
  36. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  37. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  38. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  39. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  40. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  41. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  42. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  43. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/agent_context.py +0 -0
  44. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  45. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  46. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  47. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  48. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  49. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  50. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  51. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  52. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/execution_report.py +0 -0
  53. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  54. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  55. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  56. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  57. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  58. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  59. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  60. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  61. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  62. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  63. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  64. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  65. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  66. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  67. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  68. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  69. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  70. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  71. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  72. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  73. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/agents/stream_events.py +0 -0
  74. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/__init__.py +0 -0
  75. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  76. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  77. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/add_input.py +0 -0
  78. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  79. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  80. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  81. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  82. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  83. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  84. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  85. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  86. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  87. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  88. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  89. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  90. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  91. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  92. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  93. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  94. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  95. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  96. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  97. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  98. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  99. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  100. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  101. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  102. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  103. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  104. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  105. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/source_digest.py +0 -0
  106. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  107. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  108. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  109. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  110. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  111. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  112. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  113. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  114. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/enums/__init__.py +0 -0
  115. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/enums/causality_preset.py +0 -0
  116. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/enums/di.py +0 -0
  117. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/events/__init__.py +0 -0
  118. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/events/graph_event.py +0 -0
  119. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  120. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/exceptions/__init__.py +0 -0
  121. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  122. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  123. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/__init__.py +0 -0
  124. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  125. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  126. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  127. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  128. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  129. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  130. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  131. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  132. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  133. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  134. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/case.py +0 -0
  135. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  136. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  137. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  138. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/input.py +0 -0
  139. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  140. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  141. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  142. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  143. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  144. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  145. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  146. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  147. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  148. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  149. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  150. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  151. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  152. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  153. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  154. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  155. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  156. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  157. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  158. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  159. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  160. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  161. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  162. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  163. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  164. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  165. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  166. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  167. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  168. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  169. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  170. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  171. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  172. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  173. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  174. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  175. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  176. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  177. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  178. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  179. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  180. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  181. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  182. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  183. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  184. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/scope_context.py +0 -0
  185. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  186. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  187. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  188. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/protocols/__init__.py +0 -0
  189. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/protocols/has_config.py +0 -0
  190. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  191. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/settings.py +0 -0
  192. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/__init__.py +0 -0
  193. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/bedrock_provider.py +0 -0
  194. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/concurrency.py +0 -0
  195. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/dc_replace.py +0 -0
  196. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  197. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/edge_context.py +0 -0
  198. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/effect_logger.py +0 -0
  199. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/input_context.py +0 -0
  200. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/order_transitions.py +0 -0
  201. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  202. {dialectical_framework-1.6.7 → dialectical_framework-1.7.0}/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.7
3
+ Version: 1.7.0
4
4
  Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
5
5
  License: MIT
6
6
  Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "1.6.7"
3
+ version = "1.7.0"
4
4
  description = "A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.)."
5
5
  authors = ["Evaldas Taroza <evaldas@dialexity.com>"]
6
6
  readme = "README.md"
@@ -22,7 +22,9 @@ from dialectical_framework.agents.advisor.system_prompts import \
22
22
  from dialectical_framework.agents.agent_context import agent_scope
23
23
  from dialectical_framework.agents.conversation_facilitator import \
24
24
  ConversationFacilitator
25
+ from dialectical_framework.agents.app_spec import AppSpec, resolve_app_layer
25
26
  from dialectical_framework.agents.stream_events import StreamEvent
27
+ from dialectical_framework.agents.toolsets import merge_app_tools
26
28
 
27
29
  logger = logging.getLogger(__name__)
28
30
 
@@ -55,20 +57,32 @@ class Advisor:
55
57
 
56
58
  Usage (fresh start):
57
59
  with scope(case.sid):
58
- advisor = Advisor(app_preamble=COUNSELOR_APP)
60
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA)
59
61
  response = await advisor.chat("My son started smoking...")
60
62
 
61
63
  Usage (post-analysis, rich graph exists):
62
64
  with scope(case.sid):
63
65
  context = await DialecticalContext().resolve()
64
- advisor = Advisor(app_preamble=COUNSELOR_APP, dialectical_context=context)
66
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA, dialectical_context=context)
65
67
  response = await advisor.chat("I want to talk through what we found...")
66
68
 
67
69
  Usage (resuming conversation):
68
70
  with scope(case.sid):
69
- advisor = Advisor(app_preamble=COUNSELOR_APP, messages=saved_messages)
71
+ advisor = Advisor(app_preamble=COUNSELOR_PERSONA, messages=saved_messages)
70
72
  response = await advisor.chat("What about the other angle?")
71
73
 
74
+ Usage (app-provided domain tools):
75
+ # The app brings field knowledge two ways: prose in the preamble,
76
+ # and callable resources as app @llm.tool functions. The engine
77
+ # prompt carries no docs for app tools (their tool-schema docstrings
78
+ # travel to the LLM automatically) — introduce them and their usage
79
+ # rules in the app preamble, where domain vocabulary lives.
80
+ with scope(case.sid):
81
+ advisor = Advisor(
82
+ app_preamble=ASTRO_COUNSELOR_PERSONA, # explains when to consult the chart
83
+ app_tools=[lookup_natal_chart], # @llm.tool from the app
84
+ )
85
+
72
86
  Usage (Advisor mode of an exploration session — Explorer handover):
73
87
  # User was chatting in Explorer (operator mode) and asks "what does
74
88
  # this all mean for me?" — the host toggles to counsel mode by
@@ -76,7 +90,7 @@ class Advisor:
76
90
  # exploration:
77
91
  with scope(case.sid):
78
92
  advisor = Advisor(
79
- app_preamble=EXPLORATION_ADVISOR_APP,
93
+ app_preamble=NAVIGATOR_APP_EXPLORER_AGENT_COUNSELOR_REGISTER,
80
94
  nexus_hash=explorer.nexus_hash,
81
95
  messages=explorer.messages,
82
96
  )
@@ -104,13 +118,28 @@ class Advisor:
104
118
  dialectical_context: Optional[str] = None,
105
119
  messages: Optional[list] = None,
106
120
  nexus_hash: Optional[str] = None,
121
+ app_tools: Optional[list] = None,
122
+ app: Optional[AppSpec] = None,
107
123
  ) -> None:
108
124
  self._nexus_hash = nexus_hash
125
+ # app: declarative app definition — composition depends on the mode:
126
+ # counsel toggle (nexus_hash set) keeps the Navigator contract
127
+ # (NAVIGATOR_APP_EXPLORER_AGENT_COUNSELOR_REGISTER + voicing + tool_guide); standalone uses
128
+ # the spec's advisor_persona (machinery hidden). See AppSpec.
129
+ app_preamble, app_tools = resolve_app_layer(
130
+ app,
131
+ app_preamble,
132
+ app_tools,
133
+ preamble_for="advisor_scoped" if nexus_hash else "advisor_unscoped",
134
+ )
109
135
  if nexus_hash:
110
136
  self._validate_nexus(nexus_hash)
111
137
  self._tools = _build_scoped_tools(nexus_hash)
112
138
  else:
113
139
  self._tools = _build_tools()
140
+ # App-provided @llm.tool functions (domain resources: chart lookups,
141
+ # methodology references, ...) — see toolsets.merge_app_tools.
142
+ self._tools = merge_app_tools(self._tools, app_tools)
114
143
  self._conversation = ConversationFacilitator(tools=self._tools)
115
144
  if messages:
116
145
  self._conversation._messages = list(messages)
@@ -209,6 +238,8 @@ def _build_tools() -> list:
209
238
  from dialectical_framework.agents.advisor.tools.deepen import deepen
210
239
  from dialectical_framework.agents.advisor.tools.explore import explore
211
240
  from dialectical_framework.agents.advisor.tools.ingest import ingest
241
+ from dialectical_framework.agents.advisor.tools.record_decision import \
242
+ record_decision
212
243
  from dialectical_framework.agents.advisor.tools.sync import sync
213
244
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
214
245
  inspect_node
@@ -222,6 +253,7 @@ def _build_tools() -> list:
222
253
  anchor,
223
254
  explore,
224
255
  deepen,
256
+ record_decision,
225
257
  sync,
226
258
  inspect_node,
227
259
  read_digest,
@@ -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,62 @@
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)
@@ -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
@@ -40,6 +42,8 @@ def build_scoped_tools(nexus_hash: str) -> list:
40
42
  DB-free for signature tests).
41
43
  """
42
44
  from dialectical_framework.agents.advisor.tools.anchor import anchor
45
+ from dialectical_framework.agents.advisor.tools.record_decision import \
46
+ record_decision
43
47
  from dialectical_framework.agents.orchestrator.tools.inspect_node import \
44
48
  inspect_node
45
49
  from dialectical_framework.agents.orchestrator.tools.read_digest import \
@@ -58,10 +62,10 @@ def build_scoped_tools(nexus_hash: str) -> list:
58
62
 
59
63
  @llm.tool
60
64
  async def discard(
61
- hash: Annotated[str, Field(description="Hash (or prefix) of the Statement or Perspective to discard")],
65
+ hash: Annotated[str, Field(description="Hash (or prefix) of the Statement, Perspective, or Decision to discard")],
62
66
  reason: Annotated[str, Field(description="Why it's being discarded")] = "discarded",
63
67
  ) -> 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."""
68
+ """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
69
  from dialectical_framework.concerns.discard import Discard
66
70
 
67
71
  refusal = _outside_scope_refusal(pinned_hash, hash)
@@ -107,7 +111,10 @@ def build_scoped_tools(nexus_hash: str) -> list:
107
111
 
108
112
  return await run_deepen(wheel_hash)
109
113
 
110
- return [anchor, sync, inspect_node, read_digest, discard, explore, deepen]
114
+ # record_decision is appended as-is: decisions are Case-level facts, not
115
+ # exploration members — no nexus-scope guard applies (grounds may be
116
+ # exploration members; grounding is read-only w.r.t. the exploration).
117
+ return [anchor, sync, inspect_node, read_digest, discard, explore, deepen, record_decision]
111
118
 
112
119
 
113
120
  def _outside_scope_refusal(nexus_hash: str, target_hash: str) -> str | None:
@@ -132,8 +139,8 @@ def _outside_scope_refusal(nexus_hash: str, target_hash: str) -> str | None:
132
139
 
133
140
  node = NodeRepository().find_by_hash(target_hash)
134
141
  if not isinstance(node, Perspective):
135
- # Not a perspective (likely a statement) — let Discard's own
136
- # guards handle it.
142
+ # Not a perspective (a statement or a decision) — let Discard's own
143
+ # guards handle it; decisions are Case-level, so no nexus pin applies.
137
144
  return None
138
145
  pp = node
139
146
 
@@ -20,7 +20,9 @@ from dialectical_framework.agents.analyst.system_prompts import SYSTEM_PROMPT
20
20
  from dialectical_framework.agents.conversation_facilitator import \
21
21
  ConversationFacilitator
22
22
  from dialectical_framework.agents.reasonable_concern import ReasonableConcern
23
+ from dialectical_framework.agents.app_spec import AppSpec, resolve_app_layer
23
24
  from dialectical_framework.agents.stream_events import StreamEvent
25
+ from dialectical_framework.agents.toolsets import merge_app_tools
24
26
 
25
27
  if TYPE_CHECKING:
26
28
  pass
@@ -48,13 +50,17 @@ class Analyst:
48
50
 
49
51
  Usage:
50
52
  with scope(case.sid):
51
- analyst = Analyst(app_preamble="You are a counselor...")
53
+ analyst = Analyst(app=MY_APP_SPEC) # declarative (see AppSpec)
52
54
  response = await analyst.chat("I'm struggling with work-life balance")
53
55
 
54
56
  # Resuming with history:
55
57
  with scope(case.sid):
56
58
  analyst = Analyst(messages=loaded_messages)
57
59
  response = await analyst.chat("What about the second tension?")
60
+
61
+ # Manual preamble control (app_preamble replaces AppSpec composition):
62
+ with scope(case.sid):
63
+ analyst = Analyst(app_preamble="You are a counselor...")
58
64
  """
59
65
 
60
66
  AGENT_NAME = "analyst"
@@ -63,8 +69,17 @@ class Analyst:
63
69
  self,
64
70
  app_preamble: Optional[str] = None,
65
71
  messages: Optional[list] = None,
72
+ app_tools: Optional[list] = None,
73
+ app: Optional[AppSpec] = None,
66
74
  ) -> None:
67
- self._tools = _build_tools()
75
+ # app: declarative app definition — the framework composes the
76
+ # Navigator preamble (NAVIGATOR_APP + voicing + tool_guide) and tool
77
+ # set from it. app_preamble/app_tools remain for manual control;
78
+ # mixing them with app= raises (see app_spec.resolve_app_layer).
79
+ app_preamble, app_tools = resolve_app_layer(
80
+ app, app_preamble, app_tools, preamble_for="navigator"
81
+ )
82
+ self._tools = merge_app_tools(_build_tools(), app_tools)
68
83
  self._conversation = ConversationFacilitator(tools=self._tools)
69
84
  if messages:
70
85
  self._conversation._messages = list(messages)
@@ -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:
@@ -143,6 +143,17 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
143
143
  )
144
144
  continue
145
145
 
146
+ # Name this reading of the tension: the generation already made
147
+ # the LLM name the axis of each diagonal pair (TetradDto) —
148
+ # persist it as the perspective's intent (the guiding question
149
+ # of THIS tetrad). Sibling tetrads on one polarity differ by
150
+ # exactly this. Set BEFORE commit: intent participates in the
151
+ # hash, so distinct readings are structurally distinct nodes.
152
+ if pp.intent is None:
153
+ reading = self._compose_reading(generator.axes)
154
+ if reading:
155
+ pp.intent = reading
156
+
146
157
  pp.commit()
147
158
  self._report.node_committed(pp)
148
159
  completed_pps.append(pp)
@@ -284,6 +295,8 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
284
295
  POSITION_A_MINUS,
285
296
  ]
286
297
  state: dict[str, str | None] = {"hash": pp.short_hash}
298
+ if pp.intent:
299
+ state["reading"] = pp.intent
287
300
  for pos in positions:
288
301
  manager = pp.get_relationship_manager_by_position(pos)
289
302
  pairs = manager.all()
@@ -356,6 +369,20 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
356
369
  return existing
357
370
  return None
358
371
 
372
+ @staticmethod
373
+ def _compose_reading(axes: dict[str, str]) -> Optional[str]:
374
+ """Compose AspectGeneration's captured axes into the perspective's
375
+ reading (intent). Both axes when they name different dimensions,
376
+ one when they agree or only one survived the disclaimer filter."""
377
+ constructive = axes.get("t_plus_vs_a_minus")
378
+ reflective = axes.get("a_plus_vs_t_minus")
379
+ named = [a for a in (constructive, reflective) if a]
380
+ if not named:
381
+ return None
382
+ if len(named) == 2 and named[0].lower() != named[1].lower():
383
+ return f"Reading along: {named[0]} / {named[1]}"
384
+ return f"Reading along: {named[0]}"
385
+
359
386
  @inject
360
387
  async def _get_input_text(
361
388
  self,