reactifact 0.12.0__tar.gz → 0.13.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 (304) hide show
  1. {reactifact-0.12.0/reactifact.egg-info → reactifact-0.13.0}/PKG-INFO +51 -29
  2. {reactifact-0.12.0 → reactifact-0.13.0}/README.md +50 -28
  3. {reactifact-0.12.0 → reactifact-0.13.0}/pyproject.toml +8 -2
  4. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/__init__.py +5 -2
  5. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/artifacts.py +37 -12
  6. reactifact-0.13.0/reactifact/authz.py +112 -0
  7. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/branching.py +1 -0
  8. reactifact-0.13.0/reactifact/budget.py +162 -0
  9. reactifact-0.13.0/reactifact/cache.py +167 -0
  10. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/chat.py +108 -7
  11. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/__init__.py +7 -2
  12. reactifact-0.13.0/reactifact/cli/eval.py +126 -0
  13. reactifact-0.13.0/reactifact/cli/scenario.py +192 -0
  14. reactifact-0.13.0/reactifact/cli/skills.py +173 -0
  15. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/commit_log.py +142 -16
  16. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/context.py +76 -1
  17. reactifact-0.13.0/reactifact/eval/__init__.py +165 -0
  18. reactifact-0.13.0/reactifact/eval/dataset.py +144 -0
  19. reactifact-0.13.0/reactifact/eval/evaluators.py +208 -0
  20. reactifact-0.13.0/reactifact/eval/judge.py +223 -0
  21. reactifact-0.13.0/reactifact/eval/online.py +435 -0
  22. reactifact-0.13.0/reactifact/eval/runner.py +322 -0
  23. reactifact-0.12.0/reactifact/eval.py → reactifact-0.13.0/reactifact/eval/scoring.py +88 -5
  24. reactifact-0.13.0/reactifact/events.py +77 -0
  25. reactifact-0.13.0/reactifact/guardrails.py +272 -0
  26. reactifact-0.13.0/reactifact/metrics.py +294 -0
  27. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/operations.py +18 -16
  28. reactifact-0.13.0/reactifact/pricing.py +33 -0
  29. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/anthropic.py +18 -1
  30. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/chat.py +15 -4
  31. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/contracts.py +4 -0
  32. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/fake.py +10 -2
  33. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/gemini.py +9 -0
  34. reactifact-0.13.0/reactifact/quota.py +215 -0
  35. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/__init__.py +27 -0
  36. reactifact-0.13.0/reactifact/recipes/conversation.py +302 -0
  37. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/resources.py +92 -4
  38. reactifact-0.13.0/reactifact/routing.py +94 -0
  39. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/runtime.py +175 -22
  40. reactifact-0.13.0/reactifact/skills/reactifact/SKILL.md +164 -0
  41. reactifact-0.13.0/reactifact/skills/reactifact/references/effects-and-state.md +83 -0
  42. reactifact-0.13.0/reactifact/skills/reactifact/references/trust-and-safety.md +96 -0
  43. reactifact-0.13.0/reactifact/skills/reactifact-agents/SKILL.md +156 -0
  44. reactifact-0.13.0/reactifact/skills/reactifact-agents/references/consume.md +109 -0
  45. reactifact-0.13.0/reactifact/skills/reactifact-agents/references/effects.md +65 -0
  46. reactifact-0.13.0/reactifact/skills/reactifact-eval/SKILL.md +160 -0
  47. reactifact-0.13.0/reactifact/skills/reactifact-eval/references/online.md +72 -0
  48. reactifact-0.13.0/reactifact/skills/reactifact-from-langchain/SKILL.md +102 -0
  49. reactifact-0.13.0/reactifact/skills/reactifact-llm/SKILL.md +159 -0
  50. reactifact-0.13.0/reactifact/skills/reactifact-llm/references/providers.md +77 -0
  51. reactifact-0.13.0/reactifact/skills/reactifact-llm/references/tools.md +83 -0
  52. reactifact-0.13.0/reactifact/skills/reactifact-observability/SKILL.md +134 -0
  53. reactifact-0.13.0/reactifact/skills/reactifact-rag/SKILL.md +150 -0
  54. reactifact-0.13.0/reactifact/skills/reactifact-rag/references/sources.md +103 -0
  55. reactifact-0.13.0/reactifact/skills/reactifact-testing/SKILL.md +114 -0
  56. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/__init__.py +13 -2
  57. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/assertions.py +116 -0
  58. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/fault.py +26 -7
  59. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/golden.py +71 -1
  60. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/lab.py +250 -12
  61. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/mock.py +80 -25
  62. reactifact-0.13.0/reactifact/testing/pytest_plugin.py +69 -0
  63. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tool_use.py +29 -3
  64. reactifact-0.13.0/reactifact/types.py +168 -0
  65. {reactifact-0.12.0 → reactifact-0.13.0/reactifact.egg-info}/PKG-INFO +51 -29
  66. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact.egg-info/SOURCES.txt +57 -1
  67. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_anthropic_provider.py +31 -0
  68. reactifact-0.13.0/tests/test_authz.py +120 -0
  69. reactifact-0.13.0/tests/test_cache.py +171 -0
  70. reactifact-0.13.0/tests/test_chat_example.py +18 -0
  71. reactifact-0.13.0/tests/test_chat_memory.py +142 -0
  72. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_cli.py +2 -0
  73. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_cli_scenario.py +24 -6
  74. reactifact-0.13.0/tests/test_compaction.py +153 -0
  75. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_devops.py +81 -1
  76. reactifact-0.13.0/tests/test_devops_online.py +114 -0
  77. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_devops_web.py +15 -0
  78. reactifact-0.13.0/tests/test_eval_dataset.py +293 -0
  79. reactifact-0.13.0/tests/test_eval_judge.py +131 -0
  80. reactifact-0.13.0/tests/test_eval_online.py +319 -0
  81. reactifact-0.13.0/tests/test_eval_trajectory.py +140 -0
  82. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_gemini_provider.py +27 -0
  83. reactifact-0.13.0/tests/test_guardrails.py +138 -0
  84. reactifact-0.13.0/tests/test_metrics.py +207 -0
  85. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_openai_provider.py +40 -0
  86. reactifact-0.13.0/tests/test_public_api.py +281 -0
  87. reactifact-0.13.0/tests/test_quota.py +214 -0
  88. reactifact-0.13.0/tests/test_recipe_conversation.py +154 -0
  89. reactifact-0.13.0/tests/test_routing.py +160 -0
  90. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_runtime_errors.py +86 -0
  91. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_sessions.py +68 -3
  92. reactifact-0.13.0/tests/test_skills.py +106 -0
  93. reactifact-0.13.0/tests/test_testing_golden.py +135 -0
  94. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_testing_lab.py +98 -0
  95. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_testing_mock.py +74 -0
  96. reactifact-0.13.0/tests/test_testing_provenance.py +91 -0
  97. reactifact-0.13.0/tests/test_testing_pytest_plugin.py +48 -0
  98. reactifact-0.13.0/tests/test_token_budget.py +182 -0
  99. reactifact-0.13.0/tests/test_type_registry.py +178 -0
  100. reactifact-0.12.0/reactifact/budget.py +0 -43
  101. reactifact-0.12.0/reactifact/cli/scenario.py +0 -94
  102. reactifact-0.12.0/reactifact/events.py +0 -34
  103. {reactifact-0.12.0 → reactifact-0.13.0}/LICENSE +0 -0
  104. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/__main__.py +0 -0
  105. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/_extras.py +0 -0
  106. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/_httpx.py +0 -0
  107. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/agent_tool.py +0 -0
  108. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/agents.py +0 -0
  109. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/audit.py +0 -0
  110. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/checkpoints.py +0 -0
  111. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/branch.py +0 -0
  112. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/common.py +0 -0
  113. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/context.py +0 -0
  114. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/graph.py +0 -0
  115. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/replay.py +0 -0
  116. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/cli/trace.py +0 -0
  117. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/commit.py +0 -0
  118. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/consume.py +0 -0
  119. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/context_builder.py +0 -0
  120. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/effects.py +0 -0
  121. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/interrupt.py +0 -0
  122. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/llm_agent.py +0 -0
  123. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/mcp/__init__.py +0 -0
  124. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/mcp/client.py +0 -0
  125. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/mcp/oauth.py +0 -0
  126. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/mcp/server.py +0 -0
  127. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/native_tool_use.py +0 -0
  128. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/patches.py +0 -0
  129. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/produce.py +0 -0
  130. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/prompts.py +0 -0
  131. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/__init__.py +0 -0
  132. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/_retry.py +0 -0
  133. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/azure.py +0 -0
  134. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/cerebras.py +0 -0
  135. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/deepseek.py +0 -0
  136. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/fireworks.py +0 -0
  137. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/github_models.py +0 -0
  138. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/groq.py +0 -0
  139. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/image.py +0 -0
  140. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/mistral.py +0 -0
  141. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/nvidia.py +0 -0
  142. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/ollama.py +0 -0
  143. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/openai.py +0 -0
  144. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/openrouter.py +0 -0
  145. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/perplexity.py +0 -0
  146. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/qwen.py +0 -0
  147. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/speech.py +0 -0
  148. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/together.py +0 -0
  149. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/video.py +0 -0
  150. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/xai.py +0 -0
  151. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/providers/zai.py +0 -0
  152. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/py.typed +0 -0
  153. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/__init__.py +0 -0
  154. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/_shared.py +0 -0
  155. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/agent.py +0 -0
  156. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/chat.py +0 -0
  157. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/models.py +0 -0
  158. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/rag.py +0 -0
  159. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/quick/tools_agent.py +0 -0
  160. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/cleanup.py +0 -0
  161. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/identity.py +0 -0
  162. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/inputs.py +0 -0
  163. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/memory.py +0 -0
  164. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/plan_execute.py +0 -0
  165. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/reflection.py +0 -0
  166. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/resolve.py +0 -0
  167. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/rollback.py +0 -0
  168. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/search.py +0 -0
  169. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/skills.py +0 -0
  170. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/status.py +0 -0
  171. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/supervisor.py +0 -0
  172. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/text.py +0 -0
  173. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/recipes/tool_loop.py +0 -0
  174. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/redaction.py +0 -0
  175. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/relations.py +0 -0
  176. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/replay.py +0 -0
  177. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/request.py +0 -0
  178. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/scheduler.py +0 -0
  179. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/session.py +0 -0
  180. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/sources.py +0 -0
  181. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/streaming.py +0 -0
  182. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/structured.py +0 -0
  183. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/exceptions.py +0 -0
  184. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/record.py +0 -0
  185. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/testing/registry.py +0 -0
  186. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tools.py +0 -0
  187. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/__init__.py +0 -0
  188. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/_otlp.py +0 -0
  189. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/columns.py +0 -0
  190. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/langfuse.py +0 -0
  191. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/models.py +0 -0
  192. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/otlp.py +0 -0
  193. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/postgres.py +0 -0
  194. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/store.py +0 -0
  195. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/templates/app.css +0 -0
  196. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/templates/sessions.html +0 -0
  197. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/templates/ui.html +0 -0
  198. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/templates/ui_run.html +0 -0
  199. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/tracer.py +0 -0
  200. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/tracing/web.py +0 -0
  201. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/triggers.py +0 -0
  202. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/verify.py +0 -0
  203. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/viz.py +0 -0
  204. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact/web.py +0 -0
  205. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact.egg-info/dependency_links.txt +0 -0
  206. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact.egg-info/entry_points.txt +0 -0
  207. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact.egg-info/requires.txt +0 -0
  208. {reactifact-0.12.0 → reactifact-0.13.0}/reactifact.egg-info/top_level.txt +0 -0
  209. {reactifact-0.12.0 → reactifact-0.13.0}/setup.cfg +0 -0
  210. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_absent_consume.py +0 -0
  211. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_adaptive.py +0 -0
  212. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_agent_tool.py +0 -0
  213. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_artifacts.py +0 -0
  214. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_audit.py +0 -0
  215. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_backbone.py +0 -0
  216. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_branching.py +0 -0
  217. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_budget.py +0 -0
  218. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_canonical_ports.py +0 -0
  219. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_chat_web.py +0 -0
  220. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_checkpoint.py +0 -0
  221. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_checkpoints_concurrency.py +0 -0
  222. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_commit_log.py +0 -0
  223. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_concurrency.py +0 -0
  224. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_consumes_produces.py +0 -0
  225. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_context_builder.py +0 -0
  226. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_correlated_consume.py +0 -0
  227. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_debounce.py +0 -0
  228. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_determinism.py +0 -0
  229. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_effects.py +0 -0
  230. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_eval.py +0 -0
  231. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_fintech_audit.py +0 -0
  232. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_forklab.py +0 -0
  233. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_forklab_web.py +0 -0
  234. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_friendly_api.py +0 -0
  235. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_golden.py +0 -0
  236. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_hitl.py +0 -0
  237. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_image_provider.py +0 -0
  238. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_incident_commander.py +0 -0
  239. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_invalidation.py +0 -0
  240. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_join_consume.py +0 -0
  241. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_knowledge.py +0 -0
  242. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_knowledge_web.py +0 -0
  243. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_llm_ladder.py +0 -0
  244. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_loop_bound_client.py +0 -0
  245. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_mcp.py +0 -0
  246. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_medic_lab.py +0 -0
  247. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_medic_lab_web.py +0 -0
  248. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_multisource.py +0 -0
  249. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_native_tool_use.py +0 -0
  250. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_patches.py +0 -0
  251. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_ports.py +0 -0
  252. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_produce_styles.py +0 -0
  253. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_produce_trigger.py +0 -0
  254. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_prompts.py +0 -0
  255. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_provider_auth.py +0 -0
  256. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_provider_retry.py +0 -0
  257. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_providers_integration.py +0 -0
  258. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_quick.py +0 -0
  259. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_reacts_to.py +0 -0
  260. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipe_plan_execute.py +0 -0
  261. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipe_reflection.py +0 -0
  262. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipe_supervisor.py +0 -0
  263. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipe_tool_loop.py +0 -0
  264. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipes.py +0 -0
  265. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipes_inputs.py +0 -0
  266. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipes_memory.py +0 -0
  267. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_recipes_skills.py +0 -0
  268. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_redaction.py +0 -0
  269. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_relation_graph.py +0 -0
  270. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_relations.py +0 -0
  271. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_repair.py +0 -0
  272. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_repair_web.py +0 -0
  273. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_replay.py +0 -0
  274. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_repo_agent.py +0 -0
  275. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_request.py +0 -0
  276. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_research.py +0 -0
  277. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_resources.py +0 -0
  278. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_retry.py +0 -0
  279. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_runtime.py +0 -0
  280. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_scheduler_semantics.py +0 -0
  281. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_sources.py +0 -0
  282. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_sources_search.py +0 -0
  283. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_sources_vector.py +0 -0
  284. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_speech_provider.py +0 -0
  285. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_starter_app.py +0 -0
  286. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_streaming.py +0 -0
  287. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_structured.py +0 -0
  288. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_support_copilot.py +0 -0
  289. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_testing_assertions.py +0 -0
  290. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_testing_registry.py +0 -0
  291. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_tools.py +0 -0
  292. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_trace_columns.py +0 -0
  293. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_trace_tags.py +0 -0
  294. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_tracing.py +0 -0
  295. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_vendor_factories.py +0 -0
  296. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_vendor_multimodal_factories.py +0 -0
  297. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_verify.py +0 -0
  298. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_video_provider.py +0 -0
  299. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_view.py +0 -0
  300. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_viz.py +0 -0
  301. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_web_source.py +0 -0
  302. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_workspace.py +0 -0
  303. {reactifact-0.12.0 → reactifact-0.13.0}/tests/test_workspace_with_sources.py +0 -0
  304. {reactifact-0.12.0 → reactifact-0.13.0}/tests/tests_checkpoints_sqlite.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactifact
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/bzdvdn/reactifact
@@ -46,7 +46,7 @@ Dynamic: license-file
46
46
  <img src="docs/img/reactifact-hero.png" alt="reactifact — Agents that react to artifacts, not graphs" width="800">
47
47
  </p>
48
48
 
49
- **Event-driven agents for Python developers — tasks wake on typed artifacts, like Celery tasks wake on messages. No graph to draw.**
49
+ **Auditable agents for Python.** Build **knowledge assistants** over docs, spreadsheets and the web — where the path per question isn't a graph you can draw. Every answer is computed, versioned and reproducible — not a string you have to trust.
50
50
 
51
51
  [![CI](https://github.com/bzdvdn/reactifact/actions/workflows/ci.yml/badge.svg)](https://github.com/bzdvdn/reactifact/actions/workflows/ci.yml)
52
52
  [![codecov](https://codecov.io/gh/bzdvdn/reactifact/graph/badge.svg)](https://codecov.io/gh/bzdvdn/reactifact)
@@ -56,29 +56,15 @@ Dynamic: license-file
56
56
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bzdvdn/reactifact)
57
57
  [![Docs](https://img.shields.io/badge/docs-bzdvdn.github.io%2Freactifact-blue)](https://bzdvdn.github.io/reactifact/)
58
58
 
59
- Python developers already know this model from Celery: define a **task**,
60
- declare what triggers it, let the runtime run it. reactifact applies it to agents — a task
61
- reacts to a **typed, versioned artifact** appearing in the context, not to a
62
- queue message you push or a graph edge you draw. The runtime derives what runs
63
- next from state.
59
+ > **Is:** a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key
60
+ > **Isn't:** a managed platform · a distributed task queue · a pre-built agent/tool marketplace
64
61
 
65
- | Celery | reactifact |
66
- | --- | --- |
67
- | a task | `@produce(Model)` — a unit of work that writes an artifact |
68
- | `delay()` / `apply_async()` | you don't call it: creating the input artifact **is** the trigger |
69
- | routing key / queue | `Consume(Type)` — which artifact type wakes the task |
70
- | chain / group / chord | several `consumes` / `produces`; the runtime derives the order |
71
- | retries, `acks_late` | guards + `Budget`, an honest `None` instead of a wrong result |
72
- | result backend | the `Context` — typed, versioned artifacts |
73
- | worker | `Runtime` |
62
+ A question like *"why did infra costs jump in Q2?"* needs a spreadsheet, a policy doc and a page off the web — and the *next* question needs a different subset. That is not one graph you can draw up front. You declare what artifacts exist and what agents can do with them; the runtime derives the order from state.
74
63
 
75
- Single process today (no broker, no worker pool) — the *model* is Celery-shaped,
76
- not its distributed runtime.
77
-
78
- On top of that model you get something a task queue doesn't: every artifact is
79
- **versioned with provenance**, so a run is reproducible (`context_hash`) and
80
- auditable (`audit.report`) for free. The model reasons; the arithmetic stays
81
- deterministic; every claim carries provenance.
64
+ Every derived artifact then links to what produced it
65
+ (`Answer —supported_by→ Evidence —extracted_from→ Doc`), so *"why did it say
66
+ that?"* is a query, not a guess — and the run is reproducible (`context_hash`)
67
+ and verifiable (`reactifact replay --verify`).
82
68
 
83
69
  ## On top: a provable answer
84
70
 
@@ -130,6 +116,14 @@ print(report_to_markdown(build_report(ctx, answer))) # hash per artifact + edge
130
116
  print(context_hash(ctx)) # reproducible fingerprint
131
117
  ```
132
118
 
119
+ ## Knowledge assistants, live
120
+
121
+ The same shape over many sources, not a fixed path — the
122
+ [`knowledge`](examples/knowledge) CLI answers a harder, multi-source question
123
+ (docs + a CSV) with a real computed number and its sources, no LLM key required:
124
+
125
+ ![CLI demo: asking "how much does gpu cost in total?" — the runtime searches docs and a spreadsheet, computes the sum, verifies it, and answers with sources.](docs/img/knowledge-cli-demo.gif)
126
+
133
127
  ![Left: a hand-wired fetch → verify → answer pipeline. Right: reactifact — search_agent and answer_agent each declare only what they consume and produce, wired together by Context, never each other.](docs/img/wiring.svg)
134
128
 
135
129
  Two agents explore independently on their own forks and merge back automatically
@@ -137,6 +131,31 @@ Two agents explore independently on their own forks and merge back automatically
137
131
 
138
132
  ![forklab demo: two strategies (depth/breadth) investigate on separate forks and merge cleanly; a second run edits the same artifact on both forks and reactifact raises MergeConflict instead of guessing, then re-merges under an explicit policy.](docs/img/forklab-demo.gif)
139
133
 
134
+ <details>
135
+ <summary><strong>If you know Celery…</strong> — the programming model, not its distributed runtime</summary>
136
+
137
+ Python developers already know this model from Celery: define a **task**,
138
+ declare what triggers it, let the runtime run it. reactifact applies it to
139
+ agents — a task reacts to a **typed, versioned artifact** appearing in the
140
+ context, not to a queue message you push or a graph edge you draw. The runtime
141
+ derives what runs next from state.
142
+
143
+ | Celery | reactifact |
144
+ | --- | --- |
145
+ | a task | `@produce(Model)` — a unit of work that writes an artifact |
146
+ | `delay()` / `apply_async()` | you don't call it: creating the input artifact **is** the trigger |
147
+ | routing key / queue | `Consume(Type)` — which artifact type wakes the task |
148
+ | chain / group / chord | several `consumes` / `produces`; the runtime derives the order |
149
+ | result backend | the `Context` — typed, versioned artifacts |
150
+ | worker | `Runtime` |
151
+
152
+ **Not inherited:** a broker, a worker pool, `acks_late`/redelivery, cross-process
153
+ exactly-once. reactifact is a library with an in-process runtime — the *model* is
154
+ Celery-shaped, the deployment is not. (Session-backed runs do survive a restart;
155
+ see [Durability & resume](docs/en/durability.md).)
156
+
157
+ </details>
158
+
140
159
  ```bash
141
160
  pip install reactifact
142
161
  ```
@@ -203,12 +222,6 @@ print(answer.data.text) # "Refunds are available within 14 d
203
222
  print("supported_by:", evidence.data.text) # provenance you can trace, not just a string in a log
204
223
  ```
205
224
 
206
- The same idea, live — the [`knowledge`](examples/knowledge) example's CLI answering a
207
- harder, multi-source question (docs + a CSV) with a real computed number and
208
- its sources, no LLM key required:
209
-
210
- ![CLI demo: asking "how much does gpu cost in total?" — the runtime searches docs and a spreadsheet, computes the sum, verifies it, and answers with sources.](docs/img/knowledge-cli-demo.gif)
211
-
212
225
  ## How it works
213
226
 
214
227
  ```text
@@ -325,6 +338,7 @@ Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/co
325
338
  - `medic-lab` — hypothesis laboratory: competing hypotheses scored, HITL steering, honest report.
326
339
  - `devops` — HITL tool agents + LLM tool router + trace dashboard.
327
340
  - `repair` — budget-aware replanning (chat/data in Russian by design).
341
+ - `chat` — minimal offline single-model multi-turn conversation (`Conversation` + bounded `ChatMemory`).
328
342
  - `forklab` — deterministic branch & merge: two strategies on their own forks, three-way merge.
329
343
  - `ledger` — offline proof of reactive recompute: edit one fact, only its real `Consume`rs re-run.
330
344
  - `llm_ladder` — the workflow from one LLM call to state-changing patches (3 levels).
@@ -355,6 +369,14 @@ uv sync --extra dev --extra web
355
369
  .venv/bin/ruff check
356
370
  ```
357
371
 
372
+ ## Status & contributing
373
+
374
+ Active, `0.13.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
375
+ (see the [roadmap](docs/roadmap.md)); releases follow
376
+ [Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
377
+ [GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
378
+ or a `good first issue`, helps more than you'd think.
379
+
358
380
  ## License
359
381
 
360
382
  MIT — see [LICENSE](LICENSE).
@@ -2,7 +2,7 @@
2
2
  <img src="docs/img/reactifact-hero.png" alt="reactifact — Agents that react to artifacts, not graphs" width="800">
3
3
  </p>
4
4
 
5
- **Event-driven agents for Python developers — tasks wake on typed artifacts, like Celery tasks wake on messages. No graph to draw.**
5
+ **Auditable agents for Python.** Build **knowledge assistants** over docs, spreadsheets and the web — where the path per question isn't a graph you can draw. Every answer is computed, versioned and reproducible — not a string you have to trust.
6
6
 
7
7
  [![CI](https://github.com/bzdvdn/reactifact/actions/workflows/ci.yml/badge.svg)](https://github.com/bzdvdn/reactifact/actions/workflows/ci.yml)
8
8
  [![codecov](https://codecov.io/gh/bzdvdn/reactifact/graph/badge.svg)](https://codecov.io/gh/bzdvdn/reactifact)
@@ -12,29 +12,15 @@
12
12
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bzdvdn/reactifact)
13
13
  [![Docs](https://img.shields.io/badge/docs-bzdvdn.github.io%2Freactifact-blue)](https://bzdvdn.github.io/reactifact/)
14
14
 
15
- Python developers already know this model from Celery: define a **task**,
16
- declare what triggers it, let the runtime run it. reactifact applies it to agents — a task
17
- reacts to a **typed, versioned artifact** appearing in the context, not to a
18
- queue message you push or a graph edge you draw. The runtime derives what runs
19
- next from state.
15
+ > **Is:** a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key
16
+ > **Isn't:** a managed platform · a distributed task queue · a pre-built agent/tool marketplace
20
17
 
21
- | Celery | reactifact |
22
- | --- | --- |
23
- | a task | `@produce(Model)` — a unit of work that writes an artifact |
24
- | `delay()` / `apply_async()` | you don't call it: creating the input artifact **is** the trigger |
25
- | routing key / queue | `Consume(Type)` — which artifact type wakes the task |
26
- | chain / group / chord | several `consumes` / `produces`; the runtime derives the order |
27
- | retries, `acks_late` | guards + `Budget`, an honest `None` instead of a wrong result |
28
- | result backend | the `Context` — typed, versioned artifacts |
29
- | worker | `Runtime` |
18
+ A question like *"why did infra costs jump in Q2?"* needs a spreadsheet, a policy doc and a page off the web — and the *next* question needs a different subset. That is not one graph you can draw up front. You declare what artifacts exist and what agents can do with them; the runtime derives the order from state.
30
19
 
31
- Single process today (no broker, no worker pool) — the *model* is Celery-shaped,
32
- not its distributed runtime.
33
-
34
- On top of that model you get something a task queue doesn't: every artifact is
35
- **versioned with provenance**, so a run is reproducible (`context_hash`) and
36
- auditable (`audit.report`) for free. The model reasons; the arithmetic stays
37
- deterministic; every claim carries provenance.
20
+ Every derived artifact then links to what produced it
21
+ (`Answer —supported_by→ Evidence —extracted_from→ Doc`), so *"why did it say
22
+ that?"* is a query, not a guess — and the run is reproducible (`context_hash`)
23
+ and verifiable (`reactifact replay --verify`).
38
24
 
39
25
  ## On top: a provable answer
40
26
 
@@ -86,6 +72,14 @@ print(report_to_markdown(build_report(ctx, answer))) # hash per artifact + edge
86
72
  print(context_hash(ctx)) # reproducible fingerprint
87
73
  ```
88
74
 
75
+ ## Knowledge assistants, live
76
+
77
+ The same shape over many sources, not a fixed path — the
78
+ [`knowledge`](examples/knowledge) CLI answers a harder, multi-source question
79
+ (docs + a CSV) with a real computed number and its sources, no LLM key required:
80
+
81
+ ![CLI demo: asking "how much does gpu cost in total?" — the runtime searches docs and a spreadsheet, computes the sum, verifies it, and answers with sources.](docs/img/knowledge-cli-demo.gif)
82
+
89
83
  ![Left: a hand-wired fetch → verify → answer pipeline. Right: reactifact — search_agent and answer_agent each declare only what they consume and produce, wired together by Context, never each other.](docs/img/wiring.svg)
90
84
 
91
85
  Two agents explore independently on their own forks and merge back automatically
@@ -93,6 +87,31 @@ Two agents explore independently on their own forks and merge back automatically
93
87
 
94
88
  ![forklab demo: two strategies (depth/breadth) investigate on separate forks and merge cleanly; a second run edits the same artifact on both forks and reactifact raises MergeConflict instead of guessing, then re-merges under an explicit policy.](docs/img/forklab-demo.gif)
95
89
 
90
+ <details>
91
+ <summary><strong>If you know Celery…</strong> — the programming model, not its distributed runtime</summary>
92
+
93
+ Python developers already know this model from Celery: define a **task**,
94
+ declare what triggers it, let the runtime run it. reactifact applies it to
95
+ agents — a task reacts to a **typed, versioned artifact** appearing in the
96
+ context, not to a queue message you push or a graph edge you draw. The runtime
97
+ derives what runs next from state.
98
+
99
+ | Celery | reactifact |
100
+ | --- | --- |
101
+ | a task | `@produce(Model)` — a unit of work that writes an artifact |
102
+ | `delay()` / `apply_async()` | you don't call it: creating the input artifact **is** the trigger |
103
+ | routing key / queue | `Consume(Type)` — which artifact type wakes the task |
104
+ | chain / group / chord | several `consumes` / `produces`; the runtime derives the order |
105
+ | result backend | the `Context` — typed, versioned artifacts |
106
+ | worker | `Runtime` |
107
+
108
+ **Not inherited:** a broker, a worker pool, `acks_late`/redelivery, cross-process
109
+ exactly-once. reactifact is a library with an in-process runtime — the *model* is
110
+ Celery-shaped, the deployment is not. (Session-backed runs do survive a restart;
111
+ see [Durability & resume](docs/en/durability.md).)
112
+
113
+ </details>
114
+
96
115
  ```bash
97
116
  pip install reactifact
98
117
  ```
@@ -159,12 +178,6 @@ print(answer.data.text) # "Refunds are available within 14 d
159
178
  print("supported_by:", evidence.data.text) # provenance you can trace, not just a string in a log
160
179
  ```
161
180
 
162
- The same idea, live — the [`knowledge`](examples/knowledge) example's CLI answering a
163
- harder, multi-source question (docs + a CSV) with a real computed number and
164
- its sources, no LLM key required:
165
-
166
- ![CLI demo: asking "how much does gpu cost in total?" — the runtime searches docs and a spreadsheet, computes the sum, verifies it, and answers with sources.](docs/img/knowledge-cli-demo.gif)
167
-
168
181
  ## How it works
169
182
 
170
183
  ```text
@@ -281,6 +294,7 @@ Prefer a notebook? [![Open in Colab](https://colab.research.google.com/assets/co
281
294
  - `medic-lab` — hypothesis laboratory: competing hypotheses scored, HITL steering, honest report.
282
295
  - `devops` — HITL tool agents + LLM tool router + trace dashboard.
283
296
  - `repair` — budget-aware replanning (chat/data in Russian by design).
297
+ - `chat` — minimal offline single-model multi-turn conversation (`Conversation` + bounded `ChatMemory`).
284
298
  - `forklab` — deterministic branch & merge: two strategies on their own forks, three-way merge.
285
299
  - `ledger` — offline proof of reactive recompute: edit one fact, only its real `Consume`rs re-run.
286
300
  - `llm_ladder` — the workflow from one LLM call to state-changing patches (3 levels).
@@ -311,6 +325,14 @@ uv sync --extra dev --extra web
311
325
  .venv/bin/ruff check
312
326
  ```
313
327
 
328
+ ## Status & contributing
329
+
330
+ Active, `0.13.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
331
+ (see the [roadmap](docs/roadmap.md)); releases follow
332
+ [Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
333
+ [GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
334
+ or a `good first issue`, helps more than you'd think.
335
+
314
336
  ## License
315
337
 
316
338
  MIT — see [LICENSE](LICENSE).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactifact"
7
- version = "0.12.0"
7
+ version = "0.13.0"
8
8
  description = "Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -99,7 +99,7 @@ reactifact = "reactifact.__main__:main"
99
99
  packages = { find = { where = ["."], include = ["reactifact*"], exclude = ["examples*", "tests*"] } }
100
100
 
101
101
  [tool.setuptools.package-data]
102
- reactifact = ["py.typed"]
102
+ reactifact = ["py.typed", "skills/*/SKILL.md", "skills/*/references/*.md"]
103
103
  "reactifact.tracing" = ["templates/*.html", "templates/*.css"]
104
104
 
105
105
  [tool.pytest.ini_options]
@@ -152,3 +152,9 @@ python_version = "3.11"
152
152
  packages = ["reactifact", "examples"]
153
153
  strict = true
154
154
  warn_unused_ignores = true
155
+
156
+ # pyright defaults its target to the interpreter it finds, which can resolve
157
+ # below the 3.11 floor this package requires — making `StrEnum`/`datetime.UTC`
158
+ # "unknown import symbol" and cascading into false positives on `RunOutcome`.
159
+ [tool.pyright]
160
+ pythonVersion = "3.11"
@@ -24,7 +24,7 @@ of the names you see by default even though they were always cheap to import
24
24
  from .agents import Agent, create_agent
25
25
  from .artifacts import Artifact
26
26
  from .branching import MergeConflict
27
- from .budget import Budget, RunOutcome, RunStats
27
+ from .budget import Budget, BudgetTracker, RunOutcome, RunStats
28
28
  from .consume import Consume, consume
29
29
  from .context import Context, View
30
30
  from .effects import Effects, Handle
@@ -48,13 +48,15 @@ from .runtime import Runtime
48
48
  from .session import Session, SessionStore
49
49
  from .tools import FunctionTool, Tool, ToolOutput, tool
50
50
  from .triggers import Trigger
51
+ from .types import register as register_type
51
52
 
52
- __version__ = "0.12.0"
53
+ __version__ = "0.13.0"
53
54
 
54
55
  __all__ = [
55
56
  "Agent",
56
57
  "Artifact",
57
58
  "Budget",
59
+ "BudgetTracker",
58
60
  "Consume",
59
61
  "Context",
60
62
  "Create",
@@ -97,5 +99,6 @@ __all__ = [
97
99
  "create_agent",
98
100
  "current_request",
99
101
  "produce",
102
+ "register_type",
100
103
  "tool",
101
104
  ]
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import importlib
4
3
  import uuid
5
4
  from datetime import UTC, datetime
6
5
  from typing import Any, Generic, TypeVar
7
6
 
8
7
  from pydantic import BaseModel
9
8
 
9
+ from .types import migrate_payload, resolve, type_id_of
10
+
10
11
  TData = TypeVar("TData", bound=BaseModel)
11
12
  ArtifactType = type[BaseModel]
12
13
 
@@ -31,12 +32,6 @@ def compute_dict_diff(old: dict[str, Any], new: dict[str, Any]) -> dict[str, Any
31
32
  return diff
32
33
 
33
34
 
34
- def _import_class(full_name: str) -> Any:
35
- module_name, class_name = full_name.rsplit(".", 1)
36
- module = importlib.import_module(module_name)
37
- return getattr(module, class_name)
38
-
39
-
40
35
  class Artifact(Generic[TData]):
41
36
  """Wrapper around a Pydantic model with versioning."""
42
37
 
@@ -48,13 +43,21 @@ class Artifact(Generic[TData]):
48
43
  ):
49
44
  self.id = id or str(uuid.uuid4())
50
45
  self.data = data
51
- self.data_type = f"{type(data).__module__}.{type(data).__qualname__}"
46
+ # Stable type id (reactifact.types): a registered `type_id`/`TYPE_ID`
47
+ # classvar, else the qualified name — so a rename/move can be tolerated
48
+ # via aliases instead of breaking a persisted session/replay.
49
+ self.data_type = type_id_of(type(data))
52
50
  self.created_at = datetime.now(UTC)
53
51
  self.updated_at = self.created_at
54
52
  self.created_by_commit = created_by_commit
55
53
  self._history: list[
56
54
  TData
57
55
  ] = [] # previous data versions (excluding the current one)
56
+ #: Absolute version counter — bumped by `update()`, independent of
57
+ #: `len(_history)`. `compact(keep_versions=…)` trims `_history` but must
58
+ #: not change `version` (it is part of `context_hash` and of commit
59
+ #: `Write`s), so it is tracked separately.
60
+ self._version = 0
58
61
  # Memoized to_dict(), keyed by version: re-derived on every save()
59
62
  # otherwise (session persistence saves after every commit, §reactifact.session)
60
63
  # even though most artifacts in a large context are unchanged since the
@@ -65,8 +68,22 @@ class Artifact(Generic[TData]):
65
68
  """Saves the current version to history and replaces the data."""
66
69
  self._history.append(self.data)
67
70
  self.data = new_data
71
+ self._version += 1
68
72
  self.updated_at = datetime.now(UTC)
69
73
 
74
+ def trim_history(self, keep: int) -> None:
75
+ """Keeps only the last `keep` previous versions (0 drops them all).
76
+
77
+ `version` (and so `context_hash`) is unaffected — only the ability to
78
+ inspect older versions via `history`/`get_all_versions`/`diff` is
79
+ bounded. Used by `Context.compact`.
80
+ """
81
+ if keep < 0:
82
+ raise ValueError("keep must be >= 0")
83
+ if len(self._history) > keep:
84
+ self._history = self._history[-keep:] if keep else []
85
+ self._dict_cache = None
86
+
70
87
  @property
71
88
  def history(self) -> list[TData]:
72
89
  """Returns a copy of the list of previous versions (excluding the current one)."""
@@ -75,7 +92,7 @@ class Artifact(Generic[TData]):
75
92
  @property
76
93
  def version(self) -> int:
77
94
  """Current version (0 – original, 1 – after the first update, etc.)"""
78
- return len(self._history)
95
+ return self._version
79
96
 
80
97
  def get_all_versions(self) -> list[TData]:
81
98
  """Returns all data versions, including the current one, from oldest to newest."""
@@ -106,6 +123,7 @@ class Artifact(Generic[TData]):
106
123
  "id": self.id,
107
124
  "data_type": self.data_type,
108
125
  "data": self.data.model_dump(mode="json"),
126
+ "version": self._version,
109
127
  "created_at": self.created_at.isoformat(),
110
128
  "updated_at": self.updated_at.isoformat(),
111
129
  "history": [v.model_dump(mode="json") for v in self._history],
@@ -116,14 +134,21 @@ class Artifact(Generic[TData]):
116
134
 
117
135
  @classmethod
118
136
  def from_dict(cls, d: dict[str, Any]) -> Artifact[Any]:
119
- model_class = _import_class(d["data_type"])
120
- data = model_class.model_validate(d["data"])
137
+ data_type = d["data_type"]
138
+ model_class = resolve(data_type)
139
+ data: Any = model_class.model_validate(migrate_payload(data_type, d["data"]))
121
140
  artifact = cls(
122
141
  data=data, id=d["id"], created_by_commit=d.get("created_by_commit")
123
142
  )
124
143
  artifact.created_at = datetime.fromisoformat(d["created_at"])
125
144
  artifact.updated_at = datetime.fromisoformat(d["updated_at"])
126
- artifact._history = [model_class.model_validate(h) for h in d["history"]]
145
+ history: list[Any] = [
146
+ model_class.model_validate(migrate_payload(data_type, h))
147
+ for h in d["history"]
148
+ ]
149
+ artifact._history = history
150
+ # Older payloads predate the explicit counter — fall back to history len.
151
+ artifact._version = int(d.get("version", len(artifact._history)))
127
152
  return artifact
128
153
 
129
154
  def __repr__(self) -> str:
@@ -0,0 +1,112 @@
1
+ """Authorization — a principal and a permission policy for a run (§57).
2
+
3
+ reactifact has no server, so unlike a hosted platform it can enforce access in
4
+ the library itself, at the points that actually matter: which artifact types an
5
+ agent may create/update/delete, which agent may run, and which tool may
6
+ execute. `RuntimeResources(principal=..., authorizer=...)` turns it on.
7
+
8
+ from reactifact.authz import PermissionPolicy, Principal
9
+
10
+ resources = RuntimeResources(
11
+ principal=Principal("alice", capabilities=("analyst",)),
12
+ authorizer=PermissionPolicy(grants={"analyst": {"run:*", "create:Answer"}}),
13
+ )
14
+
15
+ A principal is whoever the run acts for (an end user, a tenant, a service).
16
+ The default is `None`, and with no authorizer every action is allowed — authz
17
+ is opt-in and does not change existing behavior. Denials raise
18
+ `AuthorizationError` from `Runtime.arun()` (or that agent's generation is
19
+ dropped when `isolate_errors=True`).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from collections.abc import Iterable, Mapping
25
+ from dataclasses import dataclass, field
26
+ from typing import Any, Protocol, runtime_checkable
27
+
28
+ from pydantic import BaseModel, Field
29
+
30
+
31
+ class Principal(BaseModel):
32
+ """Who a run acts for: an id plus the capabilities it holds.
33
+
34
+ Kept a plain model so it composes with artifacts and sessions (it can be
35
+ seeded as an artifact, loaded from a session, or restored by a `SeedIdentity`
36
+ recipe) rather than living only in `RuntimeResources`.
37
+ """
38
+
39
+ id: str
40
+ capabilities: tuple[str, ...] = ()
41
+ metadata: dict[str, Any] = Field(default_factory=dict)
42
+
43
+
44
+ class AuthorizationError(PermissionError):
45
+ """Raised when a principal may not perform an action on a resource."""
46
+
47
+ def __init__(
48
+ self, action: str, resource: str, principal: Principal | None = None
49
+ ) -> None:
50
+ self.action = action
51
+ self.resource = resource
52
+ self.principal = principal
53
+ who = principal.id if principal is not None else "anonymous"
54
+ super().__init__(f"principal {who!r} may not {action} {resource!r}")
55
+
56
+
57
+ @runtime_checkable
58
+ class Authorizer(Protocol):
59
+ """Structural interface: `authorize(principal, action, resource) -> bool`."""
60
+
61
+ def authorize(
62
+ self, principal: Principal | None, action: str, resource: str
63
+ ) -> bool: ...
64
+
65
+
66
+ def _permission_matches(pattern: str, action: str, resource: str) -> bool:
67
+ """Matches `"action:resource"`, `"action:*"`, `"*:resource"` or `"*"`."""
68
+ if pattern == "*":
69
+ return True
70
+ if ":" not in pattern:
71
+ return pattern == action
72
+ allowed_action, _, allowed_resource = pattern.partition(":")
73
+ return (allowed_action in ("*", action)) and (allowed_resource in ("*", resource))
74
+
75
+
76
+ @dataclass
77
+ class PermissionPolicy:
78
+ """Capability-based permissions: a capability grants `action:resource` patterns.
79
+
80
+ `grants` maps a capability name to the patterns it allows, e.g.
81
+ `{"analyst": {"run:*", "create:Answer"}, "admin": {"*"}}`. A principal is
82
+ allowed an action when any of its capabilities grants a matching pattern.
83
+ With `default_allow=False` (the default) an anonymous/unknown principal is
84
+ denied; set it `True` to allow by default and use the grants as the
85
+ allowlist an audit needs.
86
+ """
87
+
88
+ grants: Mapping[str, Iterable[str]] = field(default_factory=dict)
89
+ default_allow: bool = False
90
+
91
+ def authorize(
92
+ self, principal: Principal | None, action: str, resource: str
93
+ ) -> bool:
94
+ if principal is None:
95
+ return self.default_allow
96
+ for capability in principal.capabilities:
97
+ for pattern in self.grants.get(capability, ()):
98
+ if _permission_matches(pattern, action, resource):
99
+ return True
100
+ return self.default_allow
101
+
102
+ def require(self, principal: Principal | None, action: str, resource: str) -> None:
103
+ if not self.authorize(principal, action, resource):
104
+ raise AuthorizationError(action, resource, principal)
105
+
106
+
107
+ __all__ = [
108
+ "AuthorizationError",
109
+ "Authorizer",
110
+ "PermissionPolicy",
111
+ "Principal",
112
+ ]
@@ -57,6 +57,7 @@ def clone_context(source: Context) -> Context:
57
57
  created_by_commit=artifact.created_by_commit,
58
58
  )
59
59
  new_artifact._history = [v.model_copy(deep=True) for v in artifact._history]
60
+ new_artifact._version = artifact._version
60
61
  new_artifact.created_at = artifact.created_at
61
62
  new_artifact.updated_at = artifact.updated_at
62
63
  new_ws._artifacts[artifact.id] = new_artifact