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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/PKG-INFO +1 -1
  2. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/pyproject.toml +1 -1
  3. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/conversation_facilitator.py +15 -1
  4. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/settings.py +29 -3
  5. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/bedrock_provider.py +17 -18
  6. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/use_brain.py +45 -1
  7. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/LICENSE +0 -0
  8. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/PATENTS +0 -0
  9. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/README.md +0 -0
  10. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/__init__.py +0 -0
  11. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/__init__.py +0 -0
  12. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  13. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  14. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
  15. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/migration.py +0 -0
  16. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
  17. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  18. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  19. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  20. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
  21. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  22. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  23. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
  24. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
  25. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
  26. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  27. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/agent_context.py +0 -0
  28. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  29. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  30. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  31. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  32. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  33. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
  34. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  35. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  36. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
  37. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  38. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
  39. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  40. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  41. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/app_spec.py +0 -0
  42. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/apps.py +0 -0
  43. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
  44. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/consultant/consultant.py +0 -0
  45. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/execution_report.py +0 -0
  46. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  47. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  48. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  49. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  50. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  51. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  52. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  53. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  54. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  55. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  56. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  57. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  58. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  59. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  60. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  61. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
  62. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
  63. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  64. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  65. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  66. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  67. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  68. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  69. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  70. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  71. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  72. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/stream_events.py +0 -0
  73. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/toolsets.py +0 -0
  74. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/agents/turn_timing.py +0 -0
  75. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/__init__.py +0 -0
  76. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  77. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  78. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/add_input.py +0 -0
  79. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  80. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  81. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  82. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  83. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  84. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
  85. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  86. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  87. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/attribution_check.py +0 -0
  88. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/build_status.py +0 -0
  89. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  90. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  91. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  92. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  93. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  94. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  95. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  96. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  97. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  98. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  99. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  100. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  101. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/create_nexus.py +0 -0
  102. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
  103. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
  104. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  105. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  106. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/discard.py +0 -0
  107. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  108. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  109. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
  110. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  111. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  112. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/record_decision.py +0 -0
  113. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
  114. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/source_digest.py +0 -0
  115. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  116. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  117. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/statement_headline.py +0 -0
  118. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  119. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  120. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/tetrad_candidates.py +0 -0
  121. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
  122. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/tetrad_sketch.py +0 -0
  123. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  124. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/thesis_fit_check.py +0 -0
  125. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  126. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  127. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/transformation_sketch.py +0 -0
  128. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/concerns/view_sketch.py +0 -0
  129. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  130. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/enums/__init__.py +0 -0
  131. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/enums/causality_preset.py +0 -0
  132. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/enums/di.py +0 -0
  133. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/events/__init__.py +0 -0
  134. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/events/graph_event.py +0 -0
  135. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  136. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/events/progress_event.py +0 -0
  137. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/exceptions/__init__.py +0 -0
  138. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  139. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/exceptions/provider_errors.py +0 -0
  140. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  141. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/__init__.py +0 -0
  142. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  143. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  144. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  145. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  146. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  147. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  148. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  149. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  150. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  151. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  152. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/case.py +0 -0
  153. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
  154. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/decision.py +0 -0
  155. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  156. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  157. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/input.py +0 -0
  158. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  159. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/note.py +0 -0
  160. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  161. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  162. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  163. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  164. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  165. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  166. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  167. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  168. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  169. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  170. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  171. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  172. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  173. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  174. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  175. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  176. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  177. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  178. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  179. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
  180. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  181. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  182. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  183. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  184. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  185. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  186. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  187. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  188. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  189. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  190. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  191. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  192. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  193. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/rendering.py +0 -0
  194. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  195. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  196. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  197. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
  198. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  199. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  200. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  201. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
  202. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  203. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  204. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  205. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  206. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  207. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  208. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/scope_context.py +0 -0
  209. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  210. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/views.py +0 -0
  211. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  212. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  213. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/protocols/__init__.py +0 -0
  214. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/protocols/has_config.py +0 -0
  215. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  216. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/py.typed +0 -0
  217. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/settings_context.py +0 -0
  218. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/__init__.py +0 -0
  219. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/async_drain.py +0 -0
  220. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/call_census.py +0 -0
  221. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/chunking.py +0 -0
  222. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/concurrency.py +0 -0
  223. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/dc_replace.py +0 -0
  224. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  225. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/edge_context.py +0 -0
  226. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/effect_logger.py +0 -0
  227. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/format_compat.py +0 -0
  228. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/input_context.py +0 -0
  229. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/order_transitions.py +0 -0
  230. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/progress.py +0 -0
  231. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/retry_accounting.py +0 -0
  232. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  233. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/thinking_compat.py +0 -0
  234. {dialectical_framework-2.1.2 → dialectical_framework-2.1.3}/src/dialectical_framework/utils/utterance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dialectical-framework
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
5
5
  License: MIT
6
6
  Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "2.1.2"
3
+ version = "2.1.3"
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"
@@ -35,7 +35,8 @@ from dialectical_framework.utils.call_census import record_call
35
35
  from dialectical_framework.utils.thinking_compat import conversational_round
36
36
  from dialectical_framework.utils.retry_accounting import (RetryAccount,
37
37
  retry_account)
38
- from dialectical_framework.utils.use_brain import (prefill_token_kwargs,
38
+ from dialectical_framework.utils.use_brain import (last_structured_reply_text,
39
+ prefill_token_kwargs,
39
40
  retry_transient, use_brain)
40
41
 
41
42
  from mirascope.llm import FinishReason, TextChunk, ThoughtChunk, ToolOutput
@@ -333,6 +334,15 @@ class ConversationFacilitator(SettingsAware):
333
334
  # prefill-sensitive quantity — the one prompt caching can move — is
334
335
  # `CallRecord.first_token_seconds` on the turn's first round, not this.
335
336
  self.last_submit_first_delta_s: Optional[float] = None
337
+ # The last STRUCTURED reply of this turn as the model wrote it, before
338
+ # parsing — the JSON text, or the tool call's arguments as text. `None`
339
+ # on a turn that made no structured call (a reused written reply, a
340
+ # mocked one). A DTO that validates can still carry a field the model
341
+ # cut short and continued in the next one (the first app's handoff,
342
+ # 2026-10-27); nothing in a parse can see that, but a host's own check
343
+ # can, and then it needs the bytes to say whether the split was the
344
+ # writing or the wire. See `use_brain.last_structured_reply_text`.
345
+ self.last_structured_reply_text: Optional[str] = None
336
346
  # Bumped once per submit, and captured by `submit_stream` so its `finally`
337
347
  # can tell "this turn is still the current one" from "a turn the consumer
338
348
  # walked away from, being finalised now".
@@ -522,6 +532,7 @@ class ConversationFacilitator(SettingsAware):
522
532
  self.last_tool_rounds = []
523
533
  self.last_submit_seconds = 0.0
524
534
  self.last_submit_retries = RetryAccount()
535
+ self.last_structured_reply_text = None
525
536
  # Reset here too although this path can never set it: a facilitator is
526
537
  # reused across turns, so without this a non-streaming submit would report
527
538
  # the previous STREAMED turn's figure as its own.
@@ -701,6 +712,7 @@ class ConversationFacilitator(SettingsAware):
701
712
  self.last_submit_seconds = 0.0
702
713
  self.last_submit_retries = RetryAccount()
703
714
  self.last_submit_first_delta_s = None
715
+ self.last_structured_reply_text = None
704
716
 
705
717
  # Re-entered per await instead of wrapped around the whole generator, and
706
718
  # `retry_account` explains why: a contextvar set across a `yield` installs
@@ -1442,6 +1454,8 @@ class ConversationFacilitator(SettingsAware):
1442
1454
  return messages
1443
1455
 
1444
1456
  result = await _llm_call()
1457
+ # Same task as the call, so this is THIS call's text (see use_brain).
1458
+ self.last_structured_reply_text = last_structured_reply_text()
1445
1459
  self._messages.append(
1446
1460
  llm.messages.assistant(
1447
1461
  self._assistant_history_text(result),
@@ -36,7 +36,9 @@ class Settings(BaseModel):
36
36
  `dialectical_framework.settings_context` scopes them to one asyncio context):
37
37
  `ai_model`, `reasoning_model`, and everything read through
38
38
  `SettingsAware.settings` — word limits, the thinking level, the Advisor's
39
- floors and caps, the audit switches.
39
+ floors and caps, the audit switches. `llm_stream_awaited_calls` and
40
+ `llm_stream_headers_timeout_s` are read at the provider on every call, so
41
+ they scope the same way.
40
42
 
41
43
  Per process (read once, from the base `DialecticalReasoning.setup()` installs;
42
44
  a value inside `using_settings` has no effect): `graph_db_*` (the connection is
@@ -304,7 +306,8 @@ class Settings(BaseModel):
304
306
  # Every awaited Bedrock call (`BedrockAnthropicProvider._create_async`) is
305
307
  # sent as a stream and assembled into the same `Message` before Mirascope
306
308
  # decodes it, so a stall is caught where it happens instead of at the whole
307
- # read timeout: the response must OPEN within 5s (`_STREAM_HEADERS_TIMEOUT_S`),
309
+ # read timeout: the response must OPEN within `llm_stream_headers_timeout_s`
310
+ # (5s by default),
308
311
  # content must start within `llm_read_timeout_s`, and a gap past 5s once it
309
312
  # flows is a stall. Measured on the first app's promise run (2026-10-12,
310
313
  # Sonnet 5.5, 59 cases, read timeout 20s): ~5.5% of calls hang, ALL before
@@ -314,7 +317,29 @@ class Settings(BaseModel):
314
317
  # exists as an off switch for a model or provider the measurement did not
315
318
  # cover (`docs/dev-notes/streaming-latency-and-retry.md`, "Hangs are caught
316
319
  # at the opening"). Per request.
317
- llm_stream_awaited_calls: bool = Field(default=True, description="Send awaited provider calls as streams and assemble the message, so a hung call is caught when the response fails to open (5s) rather than at the whole read timeout. On by default; off only for a model or provider where streaming misbehaves.")
320
+ llm_stream_awaited_calls: bool = Field(default=True, description="Send awaited provider calls as streams and assemble the message, so a hung call is caught when the response fails to open (`llm_stream_headers_timeout_s`) rather than at the whole read timeout. On by default; off only for a model or provider where streaming misbehaves.")
321
+
322
+ # How long a streamed awaited call may wait for the response to OPEN
323
+ # (headers, `message_start`) before it counts as a stall and is re-asked
324
+ # (`bedrock_provider._stream_to_message`; the re-ask is the stall curve's
325
+ # one, `use_brain._STALL_RETRY_MAX`). The default is the Bedrock
326
+ # measurement: the first app's promise run (2026-10-12, Sonnet 5.5, 890
327
+ # streamed calls) saw every stall (48, 5.4%) hang before the headers while
328
+ # healthy calls opened at a median 1.2s, p99 3.6s, whatever their size (6
329
+ # of 842 past 5s) — streamed, the opening is the one wait that does not
330
+ # grow with the output, so one short budget fits every call size.
331
+ #
332
+ # A setting since 2026-10-27 because the figure is per transport and
333
+ # account, not per model: on the native `anthropic/` transport the same
334
+ # 5s lost six of twelve fresh sessions in the first app (both attempts past
335
+ # the budget, with the SDK's own retry at zero so that is real
336
+ # time-to-first-byte), where on Bedrock the one re-ask had recovered every
337
+ # stall. Whether the native opening is slow (a wider budget lands the call)
338
+ # or hangs more often (only a retry would) is what the setting lets a host
339
+ # find out: widen it where the logged phase says "before headers" at a rate
340
+ # the re-ask does not absorb. Clamped to the read timeout; lowering it below
341
+ # the healthy p99 buys nothing. Per request, read at the call.
342
+ llm_stream_headers_timeout_s: float = Field(default=5.0, description="How long a streamed provider call may wait for the response to open, in seconds, before it counts as a stall and is re-asked once. 5s is the Bedrock measurement; widen it on a transport whose opening tail is longer. Never more than the read timeout.")
318
343
 
319
344
  @classmethod
320
345
  def from_partial(cls, partial_settings: Optional[Settings] = None) -> Self:
@@ -395,5 +420,6 @@ class Settings(BaseModel):
395
420
  llm_connect_timeout_s=float(os.getenv("DIALEXITY_LLM_CONNECT_TIMEOUT_S", 30.0)),
396
421
  llm_read_timeout_s=float(os.getenv("DIALEXITY_LLM_READ_TIMEOUT_S", 120.0)),
397
422
  llm_stream_awaited_calls=os.getenv("DIALEXITY_LLM_STREAM_AWAITED_CALLS", "true").lower() == "true",
423
+ llm_stream_headers_timeout_s=float(os.getenv("DIALEXITY_LLM_STREAM_HEADERS_TIMEOUT_S", 5.0)),
398
424
  effect_log_dir=os.getenv("DIALEXITY_GRAPH_LOG_DIR"),
399
425
  )
@@ -415,15 +415,6 @@ logger = logging.getLogger(__name__)
415
415
  #: so it is a backstop; the measured hangs are all caught at the opening.
416
416
  _STREAM_GAP_TIMEOUT_S = 5.0
417
417
 
418
- #: How long a streamed awaited call may wait for the response to OPEN (headers,
419
- #: `message_start`) before it counts as a stall. Measured on the first app's
420
- #: promise run (2026-10-12, Sonnet 5.5, 890 streamed calls): every stall (48,
421
- #: 5.4%) hung before the headers, while healthy calls opened at a median 1.2s,
422
- #: p99 3.6s, whatever their size (6 of 842 past 5s). Streamed, the opening is
423
- #: the one wait that does not grow with the output, so it is where a hang is
424
- #: caught: here at 5s rather than at the whole read timeout.
425
- _STREAM_HEADERS_TIMEOUT_S = 5.0
426
-
427
418
  #: Deltas that are the answer being written, as opposed to thinking or
428
419
  #: bookkeeping. The SDK's stream yields both the raw `content_block_delta` and
429
420
  #: a synthesized event per delta kind.
@@ -450,14 +441,18 @@ def _is_content_event(event: Any) -> bool:
450
441
 
451
442
 
452
443
  async def _stream_to_message(
453
- manager: _LearningStreamManager, model_name: str, read_timeout_s: float
444
+ manager: _LearningStreamManager,
445
+ model_name: str,
446
+ read_timeout_s: float,
447
+ headers_timeout_s: float,
454
448
  ) -> AnthropicMessage:
455
449
  """Consume one stream and return the assembled `Message`, policing silence.
456
450
 
457
- Three budgets: the response must OPEN within `_STREAM_HEADERS_TIMEOUT_S`
458
- (where the measured hangs are), the first content must follow within the
459
- read timeout (queueing, prefill, thinking), and once content flows a gap
460
- longer than `_STREAM_GAP_TIMEOUT_S` is a stall.
451
+ Three budgets: the response must OPEN within `headers_timeout_s`
452
+ (`Settings.llm_stream_headers_timeout_s` — where the measured hangs are,
453
+ and never more than the read timeout), the first content must follow
454
+ within the read timeout (queueing, prefill, thinking), and once content
455
+ flows a gap longer than `_STREAM_GAP_TIMEOUT_S` is a stall.
461
456
 
462
457
  The provider's `ping`s do not count as progress: a stream that only pings
463
458
  is a hang the socket's read timeout would never see. Every stall is logged
@@ -467,7 +462,7 @@ async def _stream_to_message(
467
462
  """
468
463
  loop = asyncio.get_running_loop()
469
464
  started = time.monotonic()
470
- headers_budget = min(_STREAM_HEADERS_TIMEOUT_S, read_timeout_s)
465
+ headers_budget = min(headers_timeout_s, read_timeout_s)
471
466
  try:
472
467
  stream = await asyncio.wait_for(manager.__aenter__(), timeout=headers_budget)
473
468
  except asyncio.TimeoutError as e:
@@ -476,7 +471,7 @@ async def _stream_to_message(
476
471
  model_name, time.monotonic() - started,
477
472
  )
478
473
  raise StreamReadTimeout(
479
- f"stream did not open within {headers_budget:.0f}s"
474
+ f"stream did not open within {headers_budget:g}s"
480
475
  ) from e
481
476
  except Exception as e:
482
477
  if type(e).__name__.endswith(("TimeoutError", "ReadTimeout")):
@@ -653,14 +648,18 @@ class _AnthropicTransport(AnthropicProvider):
653
648
  stream and comes back as the same assembled `Message`, so a stall is
654
649
  caught by the gap between chunks (`_stream_to_message`) instead of by a
655
650
  ceiling on the whole generation. The stream manager learns the thinking
656
- shape the same way.
651
+ shape the same way. The opening budget is read here, per request
652
+ (`llm_stream_headers_timeout_s`); the read timeout was read once, when
653
+ the client was built.
657
654
  """
658
655
  model_name = kwargs["model"]
659
- if get_current_settings().llm_stream_awaited_calls:
656
+ settings = get_current_settings()
657
+ if settings.llm_stream_awaited_calls:
660
658
  return await _stream_to_message(
661
659
  _LearningStreamManager(self.async_client, kwargs, params),
662
660
  model_name,
663
661
  self._read_timeout_s,
662
+ settings.llm_stream_headers_timeout_s,
664
663
  )
665
664
  try:
666
665
  return await self.async_client.messages.create(
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import contextvars
4
5
  import json
5
6
  import logging
6
7
  import time
@@ -262,7 +263,7 @@ def use_brain(
262
263
  )
263
264
  if has_format:
264
265
  try:
265
- return response.parse()
266
+ parsed = response.parse()
266
267
  except ParseError as e:
267
268
  salvaged = _salvage_envelope(
268
269
  response,
@@ -276,6 +277,7 @@ def use_brain(
276
277
  e,
277
278
  )
278
279
  if salvaged is not None:
280
+ _keep_structured_reply(response, format_name)
279
281
  return salvaged
280
282
  # Nothing fit, so this is about to cost a re-ask and
281
283
  # a nap. Say what the model actually sent, ONCE per
@@ -288,6 +290,8 @@ def use_brain(
288
290
  if attempt == 0:
289
291
  _log_unsalvageable(response, format_name)
290
292
  raise
293
+ _keep_structured_reply(response, format_name)
294
+ return parsed
291
295
  return response
292
296
  except ModelRefusal:
293
297
  # Never retried, and never classified: its explanation is
@@ -481,6 +485,46 @@ def _framing_leak_note(text: str) -> str:
481
485
  )
482
486
 
483
487
 
488
+ #: The text of the last structured reply parsed in this context, exactly as
489
+ #: the model wrote it. A reply that PARSES can still be wrong in a way no
490
+ #: parser sees: a string field cut mid-word with its tail written into the
491
+ #: next field (the first app's handoff of 2026-10-27, one Lithuanian card of
492
+ #: two) is valid JSON, and the DTO carries only the fields. The framework
493
+ #: cannot tell such a field from a whole one — any check would be a reading of
494
+ #: the content, which is the host's judge, not a parse — so what it does is
495
+ #: keep the bytes: a host whose own check fires reads them back through
496
+ #: `ConversationFacilitator.last_structured_reply_text` and can say whether
497
+ #: the split was the model's writing or something on the way. A ContextVar,
498
+ #: set in the caller's own task, so a draw gathered on another task never
499
+ #: overwrites it; `None` until a structured call has returned here.
500
+ _last_structured_reply: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
501
+ "last_structured_reply", default=None
502
+ )
503
+
504
+
505
+ def last_structured_reply_text() -> Optional[str]:
506
+ """The raw text of the last structured reply parsed in this context."""
507
+ return _last_structured_reply.get()
508
+
509
+
510
+ def _keep_structured_reply(response: Any, format_name: Optional[str]) -> None:
511
+ """Record a parsed reply as written, and say so at DEBUG, in full.
512
+
513
+ Whole and never clipped, unlike `_log_unsalvageable`: the tail is the part a
514
+ split-field diagnosis needs, and DEBUG is off unless a host turns it on for
515
+ exactly that. Payload text only — no prompt, no system prompt. Never
516
+ raises: this runs on the success path.
517
+ """
518
+ try:
519
+ text = response.text("") or ""
520
+ except Exception: # noqa: BLE001
521
+ return
522
+ _last_structured_reply.set(text)
523
+ logging.getLogger(__name__).debug(
524
+ "Structured %s reply as written (%d chars): %s", format_name, len(text), text
525
+ )
526
+
527
+
484
528
  def _log_unsalvageable(response: Any, format_name: Optional[str]) -> None:
485
529
  """Log what the model actually sent, so the next envelope is a one-line fix.
486
530