graphrefly 0.18.0__tar.gz → 0.20.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 (354) hide show
  1. {graphrefly-0.18.0 → graphrefly-0.20.0}/CHANGELOG.md +50 -0
  2. graphrefly-0.20.0/CLAUDE.md +61 -0
  3. {graphrefly-0.18.0 → graphrefly-0.20.0}/PKG-INFO +21 -4
  4. {graphrefly-0.18.0 → graphrefly-0.20.0}/README.md +20 -3
  5. graphrefly-0.20.0/llms.txt +114 -0
  6. {graphrefly-0.18.0 → graphrefly-0.20.0}/pyproject.toml +2 -1
  7. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/__init__.py +0 -6
  8. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/compat/async_utils.py +7 -9
  9. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/compat/asyncio_runner.py +34 -7
  10. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/compat/trio_runner.py +29 -5
  11. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/__init__.py +0 -3
  12. graphrefly-0.20.0/src/graphrefly/core/dynamic_node.py +382 -0
  13. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/meta.py +5 -1
  14. graphrefly-0.20.0/src/graphrefly/core/node.py +540 -0
  15. graphrefly-0.20.0/src/graphrefly/core/node_base.py +739 -0
  16. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/protocol.py +51 -26
  17. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/runner.py +15 -0
  18. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/__init__.py +8 -2
  19. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/adapters.py +16 -8
  20. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/cascading_cache.py +17 -7
  21. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/data_structures.py +1 -3
  22. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/resilience.py +13 -6
  23. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/sources.py +186 -10
  24. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/tier1.py +37 -41
  25. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/tier2.py +28 -29
  26. graphrefly-0.20.0/src/graphrefly/graph/__init__.py +67 -0
  27. graphrefly-0.20.0/src/graphrefly/graph/codec.py +293 -0
  28. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/graph/graph.py +172 -144
  29. graphrefly-0.20.0/src/graphrefly/graph/profile.py +120 -0
  30. graphrefly-0.20.0/src/graphrefly/graph/sizeof.py +115 -0
  31. graphrefly-0.20.0/src/graphrefly/patterns/_internal.py +64 -0
  32. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/ai.py +984 -79
  33. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/cqrs.py +4 -5
  34. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/domain_templates.py +7 -9
  35. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/harness/__init__.py +10 -0
  36. graphrefly-0.20.0/src/graphrefly/patterns/harness/bridge.py +473 -0
  37. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/harness/loop.py +153 -69
  38. graphrefly-0.20.0/src/graphrefly/patterns/harness/profile.py +84 -0
  39. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/harness/strategy.py +3 -4
  40. graphrefly-0.20.0/src/graphrefly/patterns/harness/trace.py +239 -0
  41. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/harness/types.py +7 -12
  42. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/messaging.py +3 -8
  43. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/orchestration.py +19 -11
  44. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/reduction.py +108 -4
  45. graphrefly-0.20.0/tests/__init__.py +1 -0
  46. graphrefly-0.20.0/tests/conftest.py +123 -0
  47. graphrefly-0.20.0/tests/helpers/__init__.py +1 -0
  48. graphrefly-0.20.0/tests/helpers/mock_llm.py +122 -0
  49. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_adapters_ingest.py +17 -69
  50. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_adapters_storage.py +8 -16
  51. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_core.py +20 -9
  52. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_domain_templates.py +3 -2
  53. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_dynamic_node.py +3 -3
  54. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_edge_cases.py +19 -34
  55. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_data_structures.py +2 -0
  56. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_resilience.py +23 -39
  57. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_sources.py +5 -2
  58. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_sources_http.py +16 -17
  59. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_tier1.py +102 -88
  60. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_tier2.py +155 -90
  61. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_fastapi.py +2 -2
  62. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_graph.py +36 -21
  63. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_operator_protocol_matrix.py +4 -3
  64. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_ai.py +726 -4
  65. graphrefly-0.20.0/tests/test_patterns_harness.py +1085 -0
  66. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_orchestration.py +14 -10
  67. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_reduction.py +9 -9
  68. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_protocol.py +1 -0
  69. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_reduction.py +10 -10
  70. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_sugar.py +3 -2
  71. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_versioning.py +2 -1
  72. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/astro.config.mjs +12 -0
  73. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/scripts/gen_api_docs.py +1 -1
  74. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/Header.astro +6 -2
  75. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/Sidebar.astro +4 -2
  76. graphrefly-0.20.0/website/src/content/docs/api/ReactiveCounterBundle.md +14 -0
  77. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/first_value_from.md +6 -0
  78. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_any.md +1 -1
  79. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/index.md +33 -36
  80. graphrefly-0.20.0/website/src/content/docs/api/is_local_only.md +16 -0
  81. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/is_terminal_message.md +2 -2
  82. graphrefly-0.20.0/website/src/content/docs/api/keepalive.md +21 -0
  83. graphrefly-0.20.0/website/src/content/docs/api/message_tier.md +20 -0
  84. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/partition_for_batch.md +1 -1
  85. graphrefly-0.20.0/website/src/content/docs/api/reactive_counter.md +19 -0
  86. graphrefly-0.20.0/website/src/content/docs/recipes/index.md +8 -0
  87. graphrefly-0.18.0/.claude/skills/dev-dispatch/SKILL.md +0 -119
  88. graphrefly-0.18.0/.claude/skills/parity/SKILL.md +0 -139
  89. graphrefly-0.18.0/.claude/skills/qa/SKILL.md +0 -104
  90. graphrefly-0.18.0/.gemini/skills/dev-dispatch/SKILL.md +0 -171
  91. graphrefly-0.18.0/.gemini/skills/parity/SKILL.md +0 -188
  92. graphrefly-0.18.0/CLAUDE.md +0 -77
  93. graphrefly-0.18.0/TRASH/EmitStrategy.md +0 -10
  94. graphrefly-0.18.0/TRASH/emit_with_batch.md +0 -43
  95. graphrefly-0.18.0/TRASH-FILES.md +0 -2
  96. graphrefly-0.18.0/archive/docs/DESIGN-ARCHIVE-INDEX.md +0 -79
  97. graphrefly-0.18.0/archive/docs/SESSION-access-control-actor-guard.md +0 -210
  98. graphrefly-0.18.0/archive/docs/SESSION-cross-repo-implementation-audit.md +0 -205
  99. graphrefly-0.18.0/archive/docs/SESSION-demo-test-strategy.md +0 -71
  100. graphrefly-0.18.0/archive/docs/SESSION-graphrefly-spec-design.md +0 -111
  101. graphrefly-0.18.0/archive/docs/SESSION-serialization-memory-footprint.md +0 -69
  102. graphrefly-0.18.0/archive/docs/SESSION-tier2-parity-nonlocal-forward-inner.md +0 -54
  103. graphrefly-0.18.0/archive/docs/SESSION-universal-reduction-layer.md +0 -65
  104. graphrefly-0.18.0/archive/docs/design-archive-index.jsonl +0 -14
  105. graphrefly-0.18.0/archive/optimizations/built-in-optimizations.jsonl +0 -8
  106. graphrefly-0.18.0/archive/optimizations/cross-language-notes.jsonl +0 -35
  107. graphrefly-0.18.0/archive/optimizations/parity-fixes.jsonl +0 -6
  108. graphrefly-0.18.0/archive/optimizations/qa-design-decisions.jsonl +0 -11
  109. graphrefly-0.18.0/archive/optimizations/resolved-decisions.jsonl +0 -95
  110. graphrefly-0.18.0/archive/optimizations/summary-table.jsonl +0 -49
  111. graphrefly-0.18.0/archive/roadmap/phase-0-foundation.jsonl +0 -7
  112. graphrefly-0.18.0/archive/roadmap/phase-1-graph-container.jsonl +0 -7
  113. graphrefly-0.18.0/archive/roadmap/phase-2-extra.jsonl +0 -3
  114. graphrefly-0.18.0/archive/roadmap/phase-3-resilience-data.jsonl +0 -4
  115. graphrefly-0.18.0/archive/roadmap/phase-4-domain-layers.jsonl +0 -5
  116. graphrefly-0.18.0/archive/roadmap/phase-5-framework-distribution.jsonl +0 -6
  117. graphrefly-0.18.0/archive/roadmap/phase-6-versioning.jsonl +0 -3
  118. graphrefly-0.18.0/archive/roadmap/phase-7-polish.jsonl +0 -2
  119. graphrefly-0.18.0/archive/roadmap/phase-8-reduction-layer.jsonl +0 -3
  120. graphrefly-0.18.0/archive/roadmap/phase-9-harness-sprint.jsonl +0 -1
  121. graphrefly-0.18.0/docs/docs-guidance.md +0 -247
  122. graphrefly-0.18.0/docs/optimizations.md +0 -80
  123. graphrefly-0.18.0/docs/roadmap.md +0 -161
  124. graphrefly-0.18.0/docs/test-guidance.md +0 -138
  125. graphrefly-0.18.0/llms.txt +0 -272
  126. graphrefly-0.18.0/src/graphrefly/core/dynamic_node.py +0 -774
  127. graphrefly-0.18.0/src/graphrefly/core/node.py +0 -1046
  128. graphrefly-0.18.0/src/graphrefly/graph/__init__.py +0 -33
  129. graphrefly-0.18.0/src/graphrefly/patterns/harness/bridge.py +0 -118
  130. graphrefly-0.18.0/tests/conftest.py +0 -46
  131. graphrefly-0.18.0/tests/test_patterns_harness.py +0 -383
  132. graphrefly-0.18.0/website/src/content/docs/api/message_tier.md +0 -19
  133. {graphrefly-0.18.0 → graphrefly-0.20.0}/.github/workflows/pages.yml +0 -0
  134. {graphrefly-0.18.0 → graphrefly-0.20.0}/.github/workflows/release.yml +0 -0
  135. {graphrefly-0.18.0 → graphrefly-0.20.0}/.gitignore +0 -0
  136. {graphrefly-0.18.0 → graphrefly-0.20.0}/.mise.toml +0 -0
  137. {graphrefly-0.18.0 → graphrefly-0.20.0}/CONTRIBUTING.md +0 -0
  138. {graphrefly-0.18.0 → graphrefly-0.20.0}/GEMINI.md +0 -0
  139. {graphrefly-0.18.0 → graphrefly-0.20.0}/LICENSE +0 -0
  140. {graphrefly-0.18.0 → graphrefly-0.20.0}/benchmarks/py-baseline.json +0 -0
  141. {graphrefly-0.18.0 → graphrefly-0.20.0}/docs/ADAPTER-CONTRACT.md +0 -0
  142. {graphrefly-0.18.0 → graphrefly-0.20.0}/docs/benchmark.md +0 -0
  143. {graphrefly-0.18.0 → graphrefly-0.20.0}/examples/README.md +0 -0
  144. {graphrefly-0.18.0 → graphrefly-0.20.0}/examples/basic_counter.py +0 -0
  145. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/compat/__init__.py +0 -0
  146. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/bridge.py +0 -0
  147. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/cancellation.py +0 -0
  148. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/clock.py +0 -0
  149. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/guard.py +0 -0
  150. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/subgraph_locks.py +0 -0
  151. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/sugar.py +0 -0
  152. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/timer.py +0 -0
  153. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/core/versioning.py +0 -0
  154. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/backoff.py +0 -0
  155. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/backpressure.py +0 -0
  156. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/checkpoint.py +0 -0
  157. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/composite.py +0 -0
  158. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/extra/cron.py +0 -0
  159. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/integrations/__init__.py +0 -0
  160. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/integrations/django.py +0 -0
  161. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/integrations/fastapi.py +0 -0
  162. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/__init__.py +0 -0
  163. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/graphspec.py +0 -0
  164. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/memory.py +0 -0
  165. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/reactive_layout/__init__.py +0 -0
  166. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/reactive_layout/measurement_adapters.py +0 -0
  167. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/reactive_layout/reactive_block_layout.py +0 -0
  168. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/patterns/reactive_layout/reactive_layout.py +0 -0
  169. {graphrefly-0.18.0 → graphrefly-0.20.0}/src/graphrefly/py.typed +0 -0
  170. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/bench_core.py +0 -0
  171. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_adapter_contract.py +0 -0
  172. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_backpressure.py +0 -0
  173. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_bridge.py +0 -0
  174. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_cascading_cache.py +0 -0
  175. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_concurrency.py +0 -0
  176. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_django.py +0 -0
  177. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_extra_composite.py +0 -0
  178. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_graphspec.py +0 -0
  179. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_guard.py +0 -0
  180. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_measurement_adapters.py +0 -0
  181. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_cqrs.py +0 -0
  182. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_memory.py +0 -0
  183. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_patterns_messaging.py +0 -0
  184. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_perf_smoke.py +0 -0
  185. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_reactive_block_layout.py +0 -0
  186. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_reactive_layout.py +0 -0
  187. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_regressions.py +0 -0
  188. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_runner.py +0 -0
  189. {graphrefly-0.18.0 → graphrefly-0.20.0}/tests/test_smoke.py +0 -0
  190. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/.gitignore +0 -0
  191. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/README.md +0 -0
  192. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/content.config.ts +0 -0
  193. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/package.json +0 -0
  194. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/pnpm-lock.yaml +0 -0
  195. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/public/llms.txt +0 -0
  196. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/py-api-sidebar.mjs +0 -0
  197. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/scripts/sync-docs.mjs +0 -0
  198. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/GraphreflyHero.astro +0 -0
  199. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/MobileMenuFooter.astro +0 -0
  200. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/PyodidePlayground.tsx +0 -0
  201. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/components/SiteTitle.astro +0 -0
  202. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/BackoffPreset.md +0 -0
  203. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/BackoffStrategy.md +0 -0
  204. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/CheckpointAdapter.md +0 -0
  205. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/CircuitBreaker.md +0 -0
  206. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/CircuitOpenError.md +0 -0
  207. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/CompactEntry.md +0 -0
  208. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/DeferWhen.md +0 -0
  209. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/DictCheckpointAdapter.md +0 -0
  210. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/DistillBundle.md +0 -0
  211. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/DownStrategy.md +0 -0
  212. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/Extraction.md +0 -0
  213. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/FileCheckpointAdapter.md +0 -0
  214. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/HttpBundle.md +0 -0
  215. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/JitterMode.md +0 -0
  216. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/MemoryCheckpointAdapter.md +0 -0
  217. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/Message.md +0 -0
  218. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/MessageType.md +0 -0
  219. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/Messages.md +0 -0
  220. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/NodeActions.md +0 -0
  221. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/NodeFn.md +0 -0
  222. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/NodeImpl.md +0 -0
  223. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/NodeStatus.md +0 -0
  224. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/PipeOperator.md +0 -0
  225. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/PubSubHub.md +0 -0
  226. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/ReactiveIndexBundle.md +0 -0
  227. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/ReactiveListBundle.md +0 -0
  228. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/ReactiveLogBundle.md +0 -0
  229. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/ReactiveMapBundle.md +0 -0
  230. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/SqliteCheckpointAdapter.md +0 -0
  231. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/SubscribeHints.md +0 -0
  232. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/TimeoutError.md +0 -0
  233. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/TokenBucket.md +0 -0
  234. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/VerifiableBundle.md +0 -0
  235. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/Versioned.md +0 -0
  236. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/WithBreakerBundle.md +0 -0
  237. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/WithStatusBundle.md +0 -0
  238. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/audit.md +0 -0
  239. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/batch.md +0 -0
  240. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/buffer.md +0 -0
  241. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/buffer_count.md +0 -0
  242. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/buffer_time.md +0 -0
  243. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/cache.md +0 -0
  244. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/cached.md +0 -0
  245. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/checkpoint_node_value.md +0 -0
  246. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/circuit_breaker.md +0 -0
  247. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/combine.md +0 -0
  248. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/concat.md +0 -0
  249. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/concat_map.md +0 -0
  250. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/constant.md +0 -0
  251. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/debounce.md +0 -0
  252. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/decorrelated_jitter.md +0 -0
  253. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/delay.md +0 -0
  254. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/derived.md +0 -0
  255. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/dispatch_messages.md +0 -0
  256. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/distill.md +0 -0
  257. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/distinct_until_changed.md +0 -0
  258. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/down_with_batch.md +0 -0
  259. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/effect.md +0 -0
  260. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/element_at.md +0 -0
  261. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/empty.md +0 -0
  262. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/exhaust_map.md +0 -0
  263. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/exponential.md +0 -0
  264. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/fallback.md +0 -0
  265. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/fibonacci.md +0 -0
  266. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/filter.md +0 -0
  267. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/find.md +0 -0
  268. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/first.md +0 -0
  269. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/flat_map.md +0 -0
  270. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/for_each.md +0 -0
  271. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_async_iter.md +0 -0
  272. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_awaitable.md +0 -0
  273. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_cron.md +0 -0
  274. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_event_emitter.md +0 -0
  275. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_fs_watch.md +0 -0
  276. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_git_hook.md +0 -0
  277. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_http.md +0 -0
  278. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_iter.md +0 -0
  279. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_mcp.md +0 -0
  280. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_timer.md +0 -0
  281. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_webhook.md +0 -0
  282. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/from_websocket.md +0 -0
  283. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/gate.md +0 -0
  284. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/interval.md +0 -0
  285. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/is_batching.md +0 -0
  286. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/is_phase2_message.md +0 -0
  287. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/last.md +0 -0
  288. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/linear.md +0 -0
  289. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/log_slice.md +0 -0
  290. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/map.md +0 -0
  291. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/merge.md +0 -0
  292. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/never.md +0 -0
  293. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/node.md +0 -0
  294. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/of.md +0 -0
  295. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/operator.md +0 -0
  296. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/pairwise.md +0 -0
  297. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/pausable.md +0 -0
  298. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/pipe.md +0 -0
  299. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/producer.md +0 -0
  300. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/propagates_to_meta.md +0 -0
  301. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/pubsub.md +0 -0
  302. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/race.md +0 -0
  303. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/rate_limiter.md +0 -0
  304. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/reactive_index.md +0 -0
  305. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/reactive_list.md +0 -0
  306. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/reactive_log.md +0 -0
  307. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/reactive_map.md +0 -0
  308. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/reduce.md +0 -0
  309. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/repeat.md +0 -0
  310. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/replay.md +0 -0
  311. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/rescue.md +0 -0
  312. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/resolve_backoff_preset.md +0 -0
  313. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/restore_graph_checkpoint.md +0 -0
  314. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/retry.md +0 -0
  315. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/sample.md +0 -0
  316. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/save_graph_checkpoint.md +0 -0
  317. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/scan.md +0 -0
  318. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/share.md +0 -0
  319. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/skip.md +0 -0
  320. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/start_with.md +0 -0
  321. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/state.md +0 -0
  322. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/subscribe.md +0 -0
  323. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/switch_map.md +0 -0
  324. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/take.md +0 -0
  325. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/take_until.md +0 -0
  326. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/take_while.md +0 -0
  327. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/tap.md +0 -0
  328. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/throttle.md +0 -0
  329. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/throw_error.md +0 -0
  330. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/timeout.md +0 -0
  331. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/timeout_node.md +0 -0
  332. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/to_array.md +0 -0
  333. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/to_list.md +0 -0
  334. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/to_sse.md +0 -0
  335. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/to_websocket.md +0 -0
  336. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/token_bucket.md +0 -0
  337. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/token_tracker.md +0 -0
  338. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/valve.md +0 -0
  339. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/verifiable.md +0 -0
  340. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/window.md +0 -0
  341. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/window_count.md +0 -0
  342. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/window_time.md +0 -0
  343. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/with_breaker.md +0 -0
  344. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/with_latest_from.md +0 -0
  345. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/with_max_attempts.md +0 -0
  346. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/with_status.md +0 -0
  347. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/api/zip.md +0 -0
  348. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/index.mdx +0 -0
  349. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content/docs/lab/python.mdx +0 -0
  350. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/content.config.ts +0 -0
  351. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/env.d.ts +0 -0
  352. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/src/styles/custom.css +0 -0
  353. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/theme-prototypes.html +0 -0
  354. {graphrefly-0.18.0 → graphrefly-0.20.0}/website/tsconfig.json +0 -0
@@ -2,6 +2,56 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v0.20.0 (2026-04-11)
6
+
7
+ ### Chores
8
+
9
+ - Add integrations page
10
+ ([`7f16d1b`](https://github.com/graphrefly/graphrefly-py/commit/7f16d1b9d9b5aadb5875e4bcb08e6decae469d1f))
11
+
12
+ - Update docs
13
+ ([`0f2d779`](https://github.com/graphrefly/graphrefly-py/commit/0f2d779eeddcf9eec9ed339bcae4dbf6899363d3))
14
+
15
+ ### Features
16
+
17
+ - Fix bug
18
+ ([`e5f9cc4`](https://github.com/graphrefly/graphrefly-py/commit/e5f9cc440a854cde0aac1060f02121b85ce06831))
19
+
20
+
21
+ ## v0.19.0 (2026-04-10)
22
+
23
+ ### Chores
24
+
25
+ - 9.0 initial test (needs revisit)
26
+ ([`f47a8b4`](https://github.com/graphrefly/graphrefly-py/commit/f47a8b4d32e999d4f867465f678d576184fc111d))
27
+
28
+ ### Features
29
+
30
+ - Add start
31
+ ([`4a008dc`](https://github.com/graphrefly/graphrefly-py/commit/4a008dcda7704e14be12a7111b1d0b9188c736b3))
32
+
33
+ - Address optimization items
34
+ ([`371f9e2`](https://github.com/graphrefly/graphrefly-py/commit/371f9e25e590ded4150fd6aed7e0de3933b8f0f3))
35
+
36
+ - Consolidate inspection tools
37
+ ([`94aa9af`](https://github.com/graphrefly/graphrefly-py/commit/94aa9af3e3d793103496bfcc71a0a4bbb38f5ea3))
38
+
39
+ - Fix
40
+ ([`222e66f`](https://github.com/graphrefly/graphrefly-py/commit/222e66f4d8954aa772534c9fc796eea3ba30a304))
41
+
42
+ - Fix async pytest
43
+ ([`f25ae08`](https://github.com/graphrefly/graphrefly-py/commit/f25ae0885fd824320b498fd2d923f6e4f6ebb0f5))
44
+
45
+ - Fix inspection tool
46
+ ([`09c90f2`](https://github.com/graphrefly/graphrefly-py/commit/09c90f273613fbae1fd7987397d85d4b6fabec8d))
47
+
48
+ - Fix retries and reingestions
49
+ ([`55d1a3d`](https://github.com/graphrefly/graphrefly-py/commit/55d1a3d0beff848a6ecefb054e42d194050d5c37))
50
+
51
+ - Reusable harness patterns
52
+ ([`84bafbb`](https://github.com/graphrefly/graphrefly-py/commit/84bafbb8476c1d00ae373bbc757d4bf813ce16c8))
53
+
54
+
5
55
  ## v0.18.0 (2026-04-08)
6
56
 
7
57
  ### Features
@@ -0,0 +1,61 @@
1
+ # graphrefly-py
2
+
3
+ Python implementation of the GraphReFly reactive graph protocol.
4
+
5
+ **All operational docs (roadmap, optimizations, test guidance, docs guidance, skills, archive) live in `~/src/graphrefly-ts`.** See that repo's `CLAUDE.md` for the full agent context.
6
+
7
+ ## Commands
8
+
9
+ uv workspace managed by mise. `mise trust && mise install` to set up uv. `uv sync` to install dependencies.
10
+
11
+ - Test: `uv run pytest`
12
+ - Lint: `uv run ruff check src/ tests/`
13
+ - Lint fix: `uv run ruff check --fix src/ tests/`
14
+ - Format: `uv run ruff format src/ tests/`
15
+ - Type check: `uv run mypy src/`
16
+
17
+ ## Documentation workflow (critical)
18
+
19
+ - Follow cross-language standard in `~/src/graphrefly-ts/docs/docs-guidance.md`.
20
+ - `website/src/content/docs/api/*.md` pages are generated; do not hand-edit.
21
+ - For docs updates in this repo:
22
+ 1. Update source docstrings.
23
+ 2. Run `pnpm --dir website docs:gen`.
24
+ 3. Validate with `pnpm --dir website docs:gen:check` and `pnpm --dir website sync-docs:check`.
25
+ - Keep `llms.txt` concise and source-oriented; avoid long static API inventories that drift.
26
+
27
+ ## Package naming
28
+
29
+ - Distribution name: `graphrefly-py`
30
+ - Import path: `graphrefly`
31
+
32
+ ## Layout
33
+
34
+ - `src/graphrefly/core/` — message protocol, `node` primitive, batch, sugar constructors (Phase 0)
35
+ - `src/graphrefly/graph/` — `Graph` container, describe/observe, snapshot (Phase 1+)
36
+ - `src/graphrefly/extra/` — operators, sources, data structures, resilience (Phase 2–3)
37
+ - `src/graphrefly/patterns/` — domain-layer APIs: orchestration, messaging, memory, AI, CQRS, reactive layout (Phase 4+)
38
+ - `src/graphrefly/compat/` — async runners: asyncio, trio (Phase 5+)
39
+ - `src/graphrefly/integrations/` — framework integrations: FastAPI (Phase 5+)
40
+
41
+ ## Key references
42
+
43
+ | Doc | Location |
44
+ |-----|----------|
45
+ | Behavior spec | `~/src/graphrefly/GRAPHREFLY-SPEC.md` |
46
+ | Composition guide | `~/src/graphrefly/COMPOSITION-GUIDE.md` |
47
+ | Roadmap, optimizations, test/docs guidance, skills, archive | `~/src/graphrefly-ts/` (single source of truth) |
48
+ | Reactive collaboration harness (§9.0) — 7-stage loop, gate, `promptNode`, strategy model | `~/src/graphrefly-ts/archive/docs/SESSION-reactive-collaboration-harness.md` |
49
+ | Harness engineering strategy — 8 requirements, wave plan, MCP priority | `~/src/graphrefly-ts/archive/docs/SESSION-harness-engineering-strategy.md` |
50
+ | Marketing & positioning — pillars, wave plan, reply playbooks, blog plan | `~/src/graphrefly-ts/archive/docs/SESSION-marketing-promotion-strategy.md` |
51
+ | Predecessor (patterns, concurrency) | `~/src/callbag-recharge-py` (reference only, not spec) |
52
+
53
+ ## Design invariants (spec §5.8–5.12)
54
+
55
+ 1. **No polling.** Use reactive timer sources (`from_timer`, `from_cron`).
56
+ 2. **No imperative triggers.** Use reactive `NodeInput` signals.
57
+ 3. **No raw async primitives.** Async boundaries belong in sources and runners, not node fns.
58
+ 4. **Central timer and `message_tier`.** Use `core/clock.py`; never hardcode type checks.
59
+ 5. **Phase 4+ APIs must be developer-friendly.** No protocol internals in primary surface.
60
+ 6. **Thread safety.** Per-subgraph `RLock`, per-node `_cache_lock`. Design for GIL and free-threaded Python.
61
+ 7. **No `async def` in public APIs.** Return `Node[T]`, `Graph`, `None`, or plain synchronous values.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphrefly
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Reactive harness layer for agent workflows. Describe automations in plain language, trace every decision, enforce policies, persist checkpoints. Zero dependencies.
5
5
  Project-URL: Homepage, https://py.graphrefly.dev
6
6
  Project-URL: Repository, https://github.com/graphrefly/graphrefly-py
@@ -25,15 +25,15 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  # GraphReFly
27
27
 
28
- **Describe what matters. It watches, filters, and explains — persistently.**
28
+ **The reactive harness layer for agent workflows.** Describe in plain language, review visually, run persistently, trace every decision.
29
29
 
30
- You're buried under emails, alerts, feeds, and messages. You can't process it all. GraphReFly lets you describe automations in plain language, review them visually, run them persistently, and trace every decision back to its source.
30
+ GraphReFly makes long-running human + LLM co-operation reactive, resumable, and causally explainable. State pushes downstream on change (no re-reading), nodes have lifecycles (not infinite append), and every decision has a traceable causal chain the substrate underneath tools, agents, and personal automations.
31
31
 
32
32
  [![PyPI](https://img.shields.io/pypi/v/graphrefly?color=blue)](https://pypi.org/project/graphrefly/)
33
33
  [![license](https://img.shields.io/github/license/graphrefly/graphrefly-py)](./LICENSE)
34
34
  [![Python](https://img.shields.io/pypi/pyversions/graphrefly)](https://pypi.org/project/graphrefly/)
35
35
 
36
- [Docs](https://py.graphrefly.dev) | [Spec](https://py.graphrefly.dev/spec/) | [TypeScript](https://graphrefly.dev) | [API Reference](https://py.graphrefly.dev/api/)
36
+ [Docs](https://graphrefly.dev/py/) | [Spec](https://graphrefly.dev/spec/) | [TypeScript](https://graphrefly.dev) | [Python API](https://graphrefly.dev/py/api/)
37
37
 
38
38
  ---
39
39
 
@@ -72,6 +72,23 @@ count.push(3)
72
72
 
73
73
  You describe what you need — an LLM composes a reactive graph (like SQL for data flows). The graph runs persistently, checkpoints its state, and traces every decision through a causal chain. Ask "why?" at any point and get a human-readable explanation from source to conclusion.
74
74
 
75
+ ## Harness engineering coverage
76
+
77
+ The eight requirements of a production agent harness cluster into a handful of composed blocks that sit on top of the reactive graph primitives:
78
+
79
+ | Need | GraphReFly |
80
+ |---|---|
81
+ | Context & state | `persistent_state()` — `auto_checkpoint` + `snapshot` / `restore` + incremental diff |
82
+ | Agent memory | `agent_memory()` — `distill` + vectors + knowledge graph + tiers, OpenViking decay |
83
+ | Control flow & resilience | `resilient_pipeline()` — `rate_limiter → breaker → retry → timeout → fallback`, correct ordering built in |
84
+ | Execution & policy | `guarded_execution()` — Actor / Guard ABAC + `policy()` + `budget_gate` + scoped describe |
85
+ | Observability & causality | `graph_lens()` — reactive topology, health, flow, and `why(node)` causal chains as structured data |
86
+ | Human governance | `gate` — reactive `pending` / `is_open` with `approve` / `reject` / `modify(fn, n)` |
87
+ | Verification | Multi-model eval harness with regression gates |
88
+ | Continuous improvement | Strategy model: `root_cause × intervention → success_rate` |
89
+
90
+ The library computes structured facts reactively; LLMs and UIs render them. Natural language is never the library's job — which keeps the whole stack model-agnostic and testable.
91
+
75
92
  ## Why GraphReFly?
76
93
 
77
94
  | | Redux / Zustand | RxPY | Pydantic AI | LangGraph | TC39 Signals | **GraphReFly** |
@@ -1,14 +1,14 @@
1
1
  # GraphReFly
2
2
 
3
- **Describe what matters. It watches, filters, and explains — persistently.**
3
+ **The reactive harness layer for agent workflows.** Describe in plain language, review visually, run persistently, trace every decision.
4
4
 
5
- You're buried under emails, alerts, feeds, and messages. You can't process it all. GraphReFly lets you describe automations in plain language, review them visually, run them persistently, and trace every decision back to its source.
5
+ GraphReFly makes long-running human + LLM co-operation reactive, resumable, and causally explainable. State pushes downstream on change (no re-reading), nodes have lifecycles (not infinite append), and every decision has a traceable causal chain the substrate underneath tools, agents, and personal automations.
6
6
 
7
7
  [![PyPI](https://img.shields.io/pypi/v/graphrefly?color=blue)](https://pypi.org/project/graphrefly/)
8
8
  [![license](https://img.shields.io/github/license/graphrefly/graphrefly-py)](./LICENSE)
9
9
  [![Python](https://img.shields.io/pypi/pyversions/graphrefly)](https://pypi.org/project/graphrefly/)
10
10
 
11
- [Docs](https://py.graphrefly.dev) | [Spec](https://py.graphrefly.dev/spec/) | [TypeScript](https://graphrefly.dev) | [API Reference](https://py.graphrefly.dev/api/)
11
+ [Docs](https://graphrefly.dev/py/) | [Spec](https://graphrefly.dev/spec/) | [TypeScript](https://graphrefly.dev) | [Python API](https://graphrefly.dev/py/api/)
12
12
 
13
13
  ---
14
14
 
@@ -47,6 +47,23 @@ count.push(3)
47
47
 
48
48
  You describe what you need — an LLM composes a reactive graph (like SQL for data flows). The graph runs persistently, checkpoints its state, and traces every decision through a causal chain. Ask "why?" at any point and get a human-readable explanation from source to conclusion.
49
49
 
50
+ ## Harness engineering coverage
51
+
52
+ The eight requirements of a production agent harness cluster into a handful of composed blocks that sit on top of the reactive graph primitives:
53
+
54
+ | Need | GraphReFly |
55
+ |---|---|
56
+ | Context & state | `persistent_state()` — `auto_checkpoint` + `snapshot` / `restore` + incremental diff |
57
+ | Agent memory | `agent_memory()` — `distill` + vectors + knowledge graph + tiers, OpenViking decay |
58
+ | Control flow & resilience | `resilient_pipeline()` — `rate_limiter → breaker → retry → timeout → fallback`, correct ordering built in |
59
+ | Execution & policy | `guarded_execution()` — Actor / Guard ABAC + `policy()` + `budget_gate` + scoped describe |
60
+ | Observability & causality | `graph_lens()` — reactive topology, health, flow, and `why(node)` causal chains as structured data |
61
+ | Human governance | `gate` — reactive `pending` / `is_open` with `approve` / `reject` / `modify(fn, n)` |
62
+ | Verification | Multi-model eval harness with regression gates |
63
+ | Continuous improvement | Strategy model: `root_cause × intervention → success_rate` |
64
+
65
+ The library computes structured facts reactively; LLMs and UIs render them. Natural language is never the library's job — which keeps the whole stack model-agnostic and testable.
66
+
50
67
  ## Why GraphReFly?
51
68
 
52
69
  | | Redux / Zustand | RxPY | Pydantic AI | LangGraph | TC39 Signals | **GraphReFly** |
@@ -0,0 +1,114 @@
1
+ # GraphReFly — Python package (`graphrefly`)
2
+
3
+ The reactive harness layer for agent workflows. Reactive graph runtime for human + LLM co-operation — causal tracing, persistent checkpoints, policy enforcement, zero runtime dependencies.
4
+
5
+ Keywords: harness engineering, agent harness, reactive graph, causal trace, explain_path, agent orchestration, LLM co-operation, human-in-the-loop, reactive middleware, universal reduction layer.
6
+
7
+ This file is an AI-oriented index for the Python repo. It points to source-of-truth docs and generated API references instead of maintaining a long, drift-prone inline API catalog.
8
+
9
+ ## Canonical behavior and docs authority
10
+
11
+ - Behavior spec: `~/src/graphrefly/GRAPHREFLY-SPEC.md`
12
+ - Composition guidance: `~/src/graphrefly/COMPOSITION-GUIDE.md`
13
+ - Docs conventions (cross-language): `~/src/graphrefly-ts/docs/docs-guidance.md`
14
+ - Roadmap/state (cross-language): `~/src/graphrefly-ts/docs/roadmap.md`
15
+ - Testing standard (cross-language): `~/src/graphrefly-ts/docs/test-guidance.md`
16
+
17
+ ## Active strategy docs (cross-language, live in graphrefly-ts)
18
+
19
+ - `~/src/graphrefly-ts/archive/docs/SESSION-reactive-collaboration-harness.md` — 7-stage reactive collaboration loop, gate port, `prompt_node`, `valve` rename, strategy model, `harness_loop()` factory
20
+ - `~/src/graphrefly-ts/archive/docs/SESSION-harness-engineering-strategy.md` — 8-requirement coverage, three-wave announcement plan, MCP Server priority, eval system design
21
+ - `~/src/graphrefly-ts/archive/docs/SESSION-marketing-promotion-strategy.md` — positioning pillars, wave plan, reply-marketing playbooks, competitive intel, prompt optimization algorithms, blog content plan
22
+
23
+ ## Primary links
24
+
25
+ - Python docs: https://graphrefly.dev/py/
26
+ - Python API reference: https://graphrefly.dev/py/api/
27
+ - TypeScript docs: https://graphrefly.dev
28
+
29
+ ## Package entry points
30
+
31
+ - Root: `from graphrefly import ...`
32
+ - Subpackages: `graphrefly.core`, `graphrefly.graph`, `graphrefly.extra`, `graphrefly.patterns`, `graphrefly.compat`, `graphrefly.integrations`
33
+
34
+ ## Public API by subpackage
35
+
36
+ Full export lists live in the generated API pages under `website/src/content/docs/api/`. The descriptions below explain *what lives in each subpackage and why*, so an LLM reading this cold can navigate without consuming the drift-prone catalog. Python uses snake_case (`from_timer`, `switch_map`, `agent_loop`) — the behavior parity with TypeScript holds, the naming does not.
37
+
38
+ ### `graphrefly.core` — `src/graphrefly/core/`
39
+
40
+ The minimal reactive substrate. One primitive (`node`) plus sugar constructors (`state`, `derived`, `producer`, `effect`, `pipe`, `dynamic_node`). Protocol internals: `MessageType` enum (`DATA`, `DIRTY`, `RESOLVED`, `COMPLETE`, `ERROR`, `TEARDOWN`, `INVALIDATE`, `PAUSE`, `RESUME`), dispatch (`dispatch_messages`, `is_phase2_message`), two-phase batch semantics (`batch`, `is_batching`, `partition_for_batch`, `down_with_batch`), guard / policy (`policy`, `policy_from_rules`, `compose_guards`, `GuardDenied`, `access_hint_for_guard`), actor identity (`Actor`, `system_actor`, `normalize_actor`), meta snapshots (`meta_snapshot`, `describe_node`), versioning (`create_versioning`, `advance_version`, `default_hash`, `is_v1`), and the central clock (`monotonic_ns`, `wall_clock_ns`). **Anything outside this package must not call `time.time_ns()` / `time.monotonic_ns()` directly — use `src/graphrefly/core/clock.py`.** Also holds the `Runner` protocol and default-runner resolution (`get_default_runner`, `set_default_runner`, `resolve_runner`) for asyncio/trio backends. Thread-safety invariants (per-subgraph `RLock`, per-node `_cache_lock`) are enforced here.
41
+
42
+ ### `graphrefly.graph` — `src/graphrefly/graph/`
43
+
44
+ The `Graph` container: reactive node registry with `register` / `resolve` / `describe` / `observe` / `snapshot` / `diff` / `diagram` / `spy` / `mount` / `unmount` / `teardown`. `reachable(graph, start_path, options=None)` computes the dependency closure from a node path — foundation for `explain_path` (causal walkback) and persistence scope.
45
+
46
+ ### `graphrefly.extra` — `src/graphrefly/extra/`
47
+
48
+ Everything reactive that isn't the primitive. Organized into focused modules:
49
+
50
+ - **Tier 1 operators** (`tier1.py`) — transform (`map`, `filter`, `scan`, `reduce`), limiting (`take`, `skip`, `take_while`, `take_until`), selection (`first`, `last`, `find`, `element_at`), utility (`start_with`, `tap`, `distinct_until_changed`, `pairwise`), combination (`combine`, `with_latest_from`), merging (`merge`, `zip`, `concat`, `race`).
51
+ - **Tier 2 operators** (`tier2.py`) — higher-order (`switch_map`, `concat_map`, `flat_map`, `exhaust_map`), timing (`debounce`, `throttle`, `delay`), sampling (`sample`, `audit`, `timeout`), buffering (`buffer`, `buffer_count`, `buffer_time`), windowing (`window`, `window_count`, `window_time`), `interval`, `repeat`, `pausable`, `rescue`. **Note:** the existing `gate(control)` here is a boolean control gate and is being renamed to `valve` per §9.0; the new human-approval `gate` lives under `patterns.orchestration`.
52
+ - **Sources** (`sources.py`) — synchronous (`of`, `empty`, `never`, `throw_error`, `from_iter`, `from_timer`, `from_cron`), async bridges (`from_awaitable`, `from_async_iter`), network / IO (`from_http`, `from_event_emitter`, `from_fs_watch`, `from_webhook`, `from_websocket`, `from_mcp`, `from_git_hook`), multicast / caching (`share`, `cached`, `replay`), collectors (`for_each`, `to_list`, `to_array`, `first_value_from`), sinks (`to_sse`, `to_websocket`). **All async boundaries must enter through sources** — node fns and operators never contain `async def` or raw awaitables.
53
+ - **Data structures** (`data_structures.py` + dedicated files) — `reactive_map`, `reactive_log` + `log_slice`, `reactive_index`, `reactive_list`, `pubsub`.
54
+ - **Resilience** (`resilience.py` + `backoff.py`) — `retry`, `circuit_breaker` + `CircuitOpenError`, `token_bucket`, `token_tracker`, `rate_limiter`, `with_breaker`, `with_status`; backoff strategies (`constant`, `linear`, `exponential`, `fibonacci`, `decorrelated_jitter`, `with_max_attempts`, `resolve_backoff_preset`).
55
+ - **Cron** (`cron.py`) — `parse_cron`, `matches_cron`.
56
+ - **Checkpoint** (`checkpoint.py`) — `Memory` / `Dict` / `File` / `Sqlite` adapters, `save_graph_checkpoint` / `restore_graph_checkpoint`, `checkpoint_node_value`. Auto-checkpoint is gated by `message_tier >= 3`. See `docs/ADAPTER-CONTRACT.md` for the adapter contract.
57
+ - **Composite** (`composite.py`) — `verifiable(source, opts)` wraps a node with verification tracking; `distill(raw, extract, opts)` compacts raw data into a budget-bounded summary (the reactive memory primitive that `agent_memory` plugs into).
58
+ - **Backpressure** (`backpressure.py`) — watermark controller for PAUSE/RESUME flow control.
59
+
60
+ ### `graphrefly.patterns` — `src/graphrefly/patterns/` (Phase 4+ domain APIs)
61
+
62
+ Developer-facing factories that compose primitives into recognizable patterns. **Protocol internals never surface here** — sensible defaults, minimal boilerplate, clear errors. Python uses `with batch():` context managers instead of TypeScript's callback form, and `Node.__or__` maps to the TS `pipe()`.
63
+
64
+ - **Orchestration** (`orchestration.py`) — `pipeline`, `task`, `branch`, the boolean control `gate` *(being renamed to `valve`)*, `approval`, `for_each`, `join`, `loop`, `sub_pipeline`, `sensor`, `wait`, `on_failure`. The §9.0 port of the human-approval gate (`pending` / `count` / `is_open` reactive nodes and `approve` / `reject` / `modify(fn, n=1)` / `open` / `close` methods) also lands here.
65
+ - **Messaging** (`messaging.py`) — `topic` (TopicGraph), `subscription` (SubscriptionGraph with cursor / `pull` / `ack`), `job_queue`, `job_flow`, `topic_bridge`. These are the primitives the §9.0 harness loop uses for static-topology sinks and cursor-driven readers.
66
+ - **Memory** (`memory.py`) — `collection`, `light_collection`, `vector_index`, `knowledge_graph`, `decay(source, opts)` (OpenViking exponential decay, 7-day default half-life). `decay` is the priority-scoring primitive for the §9.0 QUEUE stage.
67
+ - **AI** (`ai.py`) — `from_llm`, `streaming_prompt_node`, `stream_extractor`, `chat_stream`, `tool_registry`, `system_prompt_builder`, `llm_extractor`, `llm_consolidator`, `agent_memory` (default: distill + vectors + KG + tiers), `agent_loop` (ReAct inner loop), `knobs_as_tools`, `gauges_as_context`, `validate_graph_def`. The §9.0 `prompt_node` factory (wraps any LLM call in a derived node with retry / cache / structured output) also lives here and is what plugs into `distill` as `extract_fn` / `consolidate_fn`.
68
+ - **CQRS** (`cqrs.py`) — `cqrs(name, opts=None)` creates a commands / events / projections / sagas Graph.
69
+ - **Reactive layout** (`reactive_layout/`) — `reactive_layout`, `reactive_block_layout`, `analyze_and_measure`, `compute_line_breaks`, `compute_char_positions`. DOM-free Knuth-Plass line breaking as a `state → derived` graph.
70
+
71
+ ### `graphrefly.compat` — `src/graphrefly/compat/`
72
+
73
+ Async runner bindings. `AsyncioRunner` wraps a Graph in an asyncio event loop; `TrioRunner` does the same for trio. These are the only places allowed to orchestrate coroutines — node fns and operators stay synchronous. Runners resolve via `graphrefly.core.resolve_runner` so downstream code can inject a specific backend.
74
+
75
+ ### `graphrefly.integrations` — `src/graphrefly/integrations/`
76
+
77
+ Framework integrations. `fastapi.GraphReflyRouter(graph)` exposes `GET /describe`, `GET /snapshot`, `WS /observe` over a registered `Graph`. Keep these thin — all logic belongs in `core` / `extra` / `patterns`.
78
+
79
+ ### Design docs that back the above
80
+
81
+ - Phase 4+ factory authors must read `~/src/graphrefly/COMPOSITION-GUIDE.md` before composing primitives — covers lazy activation, subscription ordering, null guards, feedback cycles, `prompt_node` SENTINEL, wiring order.
82
+ - Backlog / anti-patterns / deferred follow-ups: `~/src/graphrefly-ts/docs/optimizations.md` (cross-language).
83
+ - Historical design decisions and parity notes: `~/src/graphrefly-ts/archive/optimizations/` (cross-language).
84
+
85
+ ## Generated docs workflow
86
+
87
+ Python API pages are generated from docstrings:
88
+
89
+ - Generator: `website/scripts/gen_api_docs.py`
90
+ - Output: `website/src/content/docs/api/*.md`
91
+ - Commands:
92
+ - `pnpm --dir website docs:gen`
93
+ - `pnpm --dir website docs:gen:check`
94
+ - `pnpm --dir website sync-docs`
95
+ - `pnpm --dir website sync-docs:check`
96
+
97
+ Do not hand-edit generated API pages.
98
+
99
+ ## Core design invariants
100
+
101
+ - No polling; propagation is reactive.
102
+ - No imperative triggers outside graph/message flow.
103
+ - No raw async primitives inside reactive layer logic.
104
+ - Use `src/graphrefly/core/clock.py` and `message_tier` helpers for timing/tier semantics.
105
+ - Keep Phase 4+ surfaces developer-oriented (hide protocol internals from primary API docs).
106
+ - Thread safety: per-subgraph `RLock`, per-node `_cache_lock`; no `async def` / `Awaitable` in public APIs.
107
+
108
+ ## Harness engineering coverage
109
+
110
+ GraphReFly covers the 8 requirements of a production agent harness — context/state control, execution boundary, control flow, verification, observability, policy/safety, human governance, continuous improvement. Mapping lives in `README.md` and `~/src/graphrefly-ts/archive/docs/SESSION-harness-engineering-strategy.md`.
111
+
112
+ ## Reactive collaboration loop (§9.0)
113
+
114
+ `harness_loop()` wires a static topology: INTAKE → TRIAGE → QUEUE → GATE → EXECUTE → VERIFY → REFLECT. Static topology + flowing data (the Kafka insight). Humans steer via `gate.modify(fn, n)`. Strategy model (`root_cause × intervention → success_rate`) feeds TRIAGE routing. Design source: `~/src/graphrefly-ts/archive/docs/SESSION-reactive-collaboration-harness.md`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graphrefly"
3
- version = "0.18.0"
3
+ version = "0.20.0"
4
4
  description = "Reactive harness layer for agent workflows. Describe automations in plain language, trace every decision, enforce policies, persist checkpoints. Zero dependencies."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -90,6 +90,7 @@ quote-style = "double"
90
90
 
91
91
  [tool.pytest.ini_options]
92
92
  testpaths = ["tests"]
93
+ pythonpath = ["tests"]
93
94
  asyncio_mode = "auto"
94
95
  addopts = "-m 'not benchmark'"
95
96
  markers = [
@@ -37,7 +37,6 @@ from graphrefly.core import (
37
37
  defer_down,
38
38
  defer_set,
39
39
  derived,
40
- describe_node,
41
40
  dispatch_messages,
42
41
  down_with_batch,
43
42
  dynamic_node,
@@ -47,7 +46,6 @@ from graphrefly.core import (
47
46
  is_batching,
48
47
  is_phase2_message,
49
48
  is_v1,
50
- meta_snapshot,
51
49
  monotonic_ns,
52
50
  node,
53
51
  normalize_actor,
@@ -74,7 +72,6 @@ from graphrefly.graph import (
74
72
  GraphDiffResult,
75
73
  GraphObserveSource,
76
74
  ObserveResult,
77
- SpyHandle,
78
75
  TraceEntry,
79
76
  reachable,
80
77
  )
@@ -93,7 +90,6 @@ __all__ = [
93
90
  "NodeVersionInfo",
94
91
  "ObserveResult",
95
92
  "PATH_SEP",
96
- "SpyHandle",
97
93
  "TraceEntry",
98
94
  "V0",
99
95
  "V1",
@@ -138,13 +134,11 @@ __all__ = [
138
134
  "compose_guards",
139
135
  "defer_down",
140
136
  "defer_set",
141
- "describe_node",
142
137
  "dispatch_messages",
143
138
  "down_with_batch",
144
139
  "ensure_registered",
145
140
  "is_batching",
146
141
  "is_phase2_message",
147
- "meta_snapshot",
148
142
  "node",
149
143
  "normalize_actor",
150
144
  "partition_for_batch",
@@ -11,6 +11,7 @@ import asyncio
11
11
  import contextlib
12
12
  from typing import TYPE_CHECKING, Any
13
13
 
14
+ from graphrefly.core.node import NO_VALUE
14
15
  from graphrefly.core.protocol import MessageType
15
16
 
16
17
  if TYPE_CHECKING:
@@ -101,13 +102,11 @@ async def first_value_from_async(source: Node[Any]) -> Any:
101
102
  assert value == 42
102
103
  """
103
104
  # Fast path: already settled with a cached value.
104
- # ``source.get()`` returns ``None`` when no value is cached, so
105
- # ``is not None`` doubles as the "has value" sentinel while still
106
- # returning falsy values like ``0``, ``False``, ``""``.
105
+ # Uses NO_VALUE sentinel so ``None`` as a real domain value is not skipped.
107
106
  status = source.status
108
107
  if status in ("settled", "resolved"):
109
- v = source.get()
110
- if v is not None:
108
+ v = getattr(source, "_cached", NO_VALUE)
109
+ if v is not NO_VALUE:
111
110
  return v
112
111
 
113
112
  loop = asyncio.get_running_loop()
@@ -163,12 +162,11 @@ async def settled(source: Node[Any]) -> Any:
163
162
  value = await settled(b)
164
163
  assert value == 20
165
164
  """
166
- # Fast path: already settled (see ``first_value_from_async`` for the
167
- # ``is not None`` rationale).
165
+ # Fast path: already settled uses NO_VALUE sentinel so ``None`` is not skipped.
168
166
  status = source.status
169
167
  if status in ("settled", "resolved"):
170
- v = source.get()
171
- if v is not None:
168
+ v = getattr(source, "_cached", NO_VALUE)
169
+ if v is not NO_VALUE:
172
170
  return v
173
171
 
174
172
  loop = asyncio.get_running_loop()
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import threading
6
7
  from typing import TYPE_CHECKING, Any
7
8
 
8
9
  if TYPE_CHECKING:
@@ -29,10 +30,12 @@ class AsyncioRunner:
29
30
  asyncio.run(main())
30
31
  """
31
32
 
32
- __slots__ = ("_loop",)
33
+ __slots__ = ("_loop", "_scheduled", "_completed")
33
34
 
34
35
  def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
35
36
  self._loop = loop
37
+ self._scheduled = 0
38
+ self._completed = 0
36
39
 
37
40
  @classmethod
38
41
  def from_running(cls) -> AsyncioRunner:
@@ -50,11 +53,12 @@ class AsyncioRunner:
50
53
  on_error: Callable[[BaseException], None],
51
54
  ) -> Callable[[], None]:
52
55
  task: asyncio.Task[Any] | None = None
53
- cancelled = False
56
+ cancelled = threading.Event()
54
57
 
55
58
  def _create_task() -> None:
56
59
  nonlocal task
57
- if cancelled:
60
+ if cancelled.is_set():
61
+ self._completed += 1
58
62
  coro.close()
59
63
  return
60
64
 
@@ -71,19 +75,42 @@ class AsyncioRunner:
71
75
  on_error(err)
72
76
  else:
73
77
  on_result(result)
78
+ finally:
79
+ self._completed += 1
74
80
 
75
81
  task = self._loop.create_task(_wrapper())
76
82
 
77
- # Thread-safe: schedule task creation on the event loop.
78
- self._loop.call_soon_threadsafe(_create_task)
83
+ # Increment eagerly on the calling thread so __repr__ is always consistent.
84
+ self._scheduled += 1
85
+ try:
86
+ self._loop.call_soon_threadsafe(_create_task)
87
+ except RuntimeError:
88
+ # Loop closed — cannot schedule; close the coroutine and balance the counter.
89
+ self._completed += 1
90
+ coro.close()
79
91
 
80
92
  def cancel() -> None:
81
- nonlocal cancelled
82
- cancelled = True
93
+ cancelled.set()
83
94
  if task is not None:
84
95
  task.cancel()
85
96
 
86
97
  return cancel
87
98
 
99
+ def would_block_deadlock(self) -> bool:
100
+ """True if blocking the current thread would starve this runner's loop."""
101
+ try:
102
+ loop = asyncio.get_running_loop()
103
+ except RuntimeError:
104
+ return False
105
+ return loop is self._loop
106
+
107
+ def __repr__(self) -> str:
108
+ pending = self._scheduled - self._completed
109
+ running = self._loop.is_running()
110
+ return (
111
+ f"AsyncioRunner(scheduled={self._scheduled}, completed={self._completed}, "
112
+ f"pending={pending}, loop_running={running})"
113
+ )
114
+
88
115
 
89
116
  __all__ = ["AsyncioRunner"]
@@ -8,6 +8,7 @@ Usage::
8
8
  from graphrefly.compat.trio_runner import TrioRunner
9
9
  from graphrefly.core.runner import set_default_runner
10
10
 
11
+
11
12
  async def main():
12
13
  async with trio.open_nursery() as nursery:
13
14
  runner = TrioRunner(nursery)
@@ -19,6 +20,7 @@ Usage::
19
20
 
20
21
  from __future__ import annotations
21
22
 
23
+ import threading
22
24
  from typing import TYPE_CHECKING, Any
23
25
 
24
26
  if TYPE_CHECKING:
@@ -34,10 +36,12 @@ class TrioRunner:
34
36
  Cancel scopes provide best-effort cancellation.
35
37
  """
36
38
 
37
- __slots__ = ("_nursery",)
39
+ __slots__ = ("_nursery", "_scheduled", "_completed")
38
40
 
39
41
  def __init__(self, nursery: trio.Nursery) -> None:
40
42
  self._nursery = nursery
43
+ self._scheduled = 0
44
+ self._completed = 0
41
45
 
42
46
  def schedule(
43
47
  self,
@@ -48,11 +52,12 @@ class TrioRunner:
48
52
  import trio as _trio
49
53
 
50
54
  cancel_scope = _trio.CancelScope()
51
- cancelled = False
55
+ cancelled = threading.Event()
52
56
 
53
57
  async def _wrapper() -> None:
54
58
  with cancel_scope:
55
- if cancelled:
59
+ if cancelled.is_set():
60
+ self._completed += 1
56
61
  coro.close()
57
62
  return
58
63
  try:
@@ -67,15 +72,34 @@ class TrioRunner:
67
72
  on_error(err)
68
73
  else:
69
74
  on_result(result)
75
+ finally:
76
+ self._completed += 1
70
77
 
78
+ self._scheduled += 1
71
79
  self._nursery.start_soon(_wrapper)
72
80
 
73
81
  def cancel() -> None:
74
- nonlocal cancelled
75
- cancelled = True
82
+ cancelled.set()
76
83
  cancel_scope.cancel()
77
84
 
78
85
  return cancel
79
86
 
87
+ def would_block_deadlock(self) -> bool:
88
+ """True if blocking the current thread would starve the trio nursery."""
89
+ try:
90
+ import trio as _trio
91
+
92
+ _trio.lowlevel.current_trio_token()
93
+ return True
94
+ except RuntimeError:
95
+ return False
96
+
97
+ def __repr__(self) -> str:
98
+ pending = self._scheduled - self._completed
99
+ return (
100
+ f"TrioRunner(scheduled={self._scheduled}, completed={self._completed}, "
101
+ f"pending={pending})"
102
+ )
103
+
80
104
 
81
105
  __all__ = ["TrioRunner"]
@@ -17,7 +17,6 @@ from graphrefly.core.guard import (
17
17
  record_mutation,
18
18
  system_actor,
19
19
  )
20
- from graphrefly.core.meta import describe_node, meta_snapshot
21
20
  from graphrefly.core.node import (
22
21
  NO_VALUE,
23
22
  Node,
@@ -112,7 +111,6 @@ __all__ = [
112
111
  "compose_guards",
113
112
  "defer_down",
114
113
  "defer_set",
115
- "describe_node",
116
114
  "dispatch_messages",
117
115
  "down_with_batch",
118
116
  "ensure_registered",
@@ -121,7 +119,6 @@ __all__ = [
121
119
  "is_terminal_message",
122
120
  "message_tier",
123
121
  "propagates_to_meta",
124
- "meta_snapshot",
125
122
  "node",
126
123
  "normalize_actor",
127
124
  "partition_for_batch",