calfkit 0.2.4__tar.gz → 0.2.6__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 (260) hide show
  1. {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/release.yml +1 -1
  2. {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/security.yml +1 -1
  3. calfkit-0.2.6/.release-please-manifest.json +3 -0
  4. {calfkit-0.2.4 → calfkit-0.2.6}/CHANGELOG.md +14 -0
  5. {calfkit-0.2.4 → calfkit-0.2.6}/PKG-INFO +43 -1
  6. {calfkit-0.2.4 → calfkit-0.2.6}/README.md +42 -0
  7. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/__init__.py +2 -1
  8. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/base.py +3 -1
  9. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/client.py +14 -2
  10. calfkit-0.2.6/calfkit/models/node_schema.py +21 -0
  11. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/session_context.py +2 -1
  12. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/state.py +9 -0
  13. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/__init__.py +2 -1
  14. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/agent.py +12 -3
  15. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/base.py +92 -19
  16. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/tool.py +27 -16
  17. {calfkit-0.2.4 → calfkit-0.2.6}/pyproject.toml +1 -1
  18. calfkit-0.2.6/tests/conftest.py +394 -0
  19. {calfkit-0.2.4 → calfkit-0.2.6}/tests/providers.py +12 -1
  20. calfkit-0.2.6/tests/test_gates.py +391 -0
  21. calfkit-0.2.6/tests/test_overrides.py +55 -0
  22. calfkit-0.2.6/tests/test_serializable.py +31 -0
  23. calfkit-0.2.4/.release-please-manifest.json +0 -3
  24. calfkit-0.2.4/tests/conftest.py +0 -157
  25. {calfkit-0.2.4 → calfkit-0.2.6}/.github/CODEOWNERS +0 -0
  26. {calfkit-0.2.4 → calfkit-0.2.6}/.github/dependabot.yml +0 -0
  27. {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/build.yml +0 -0
  28. {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/code-checks.yml +0 -0
  29. {calfkit-0.2.4 → calfkit-0.2.6}/.github/workflows/test.yml +0 -0
  30. {calfkit-0.2.4 → calfkit-0.2.6}/.gitignore +0 -0
  31. {calfkit-0.2.4 → calfkit-0.2.6}/LICENSE +0 -0
  32. {calfkit-0.2.4 → calfkit-0.2.6}/Makefile +0 -0
  33. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_types.py +0 -0
  34. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/__init__.py +0 -0
  35. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/LICENSE +0 -0
  36. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/__init__.py +0 -0
  37. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/__main__.py +0 -0
  38. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_a2a.py +0 -0
  39. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_agent_graph.py +0 -0
  40. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_cli/__init__.py +0 -0
  41. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_cli/web.py +0 -0
  42. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_function_schema.py +0 -0
  43. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_griffe.py +0 -0
  44. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_instrumentation.py +0 -0
  45. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_json_schema.py +0 -0
  46. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_mcp.py +0 -0
  47. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_otel_messages.py +0 -0
  48. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_output.py +0 -0
  49. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_parts_manager.py +0 -0
  50. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_run_context.py +0 -0
  51. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_system_prompt.py +0 -0
  52. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_thinking_part.py +0 -0
  53. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_tool_manager.py +0 -0
  54. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/_utils.py +0 -0
  55. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ag_ui.py +0 -0
  56. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/__init__.py +0 -0
  57. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/abstract.py +0 -0
  58. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/agent/wrapper.py +0 -0
  59. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/builtin_tools.py +0 -0
  60. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/__init__.py +0 -0
  61. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/duckduckgo.py +0 -0
  62. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/exa.py +0 -0
  63. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/common_tools/tavily.py +0 -0
  64. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/direct.py +0 -0
  65. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/__init__.py +0 -0
  66. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  67. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
  68. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_fastmcp_toolset.py +0 -0
  69. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp.py +0 -0
  70. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  71. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  72. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  73. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  74. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
  75. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  76. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  77. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  78. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  79. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  80. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  81. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  82. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
  83. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_dynamic_toolset.py +0 -0
  84. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_fastmcp_toolset.py +0 -0
  85. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  86. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  87. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp.py +0 -0
  88. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  89. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  90. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  91. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  92. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_workflow.py +0 -0
  93. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/__init__.py +0 -0
  94. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/base.py +0 -0
  95. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/cohere.py +0 -0
  96. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/google.py +0 -0
  97. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/instrumented.py +0 -0
  98. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/openai.py +0 -0
  99. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/result.py +0 -0
  100. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/sentence_transformers.py +0 -0
  101. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/settings.py +0 -0
  102. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/test.py +0 -0
  103. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/voyageai.py +0 -0
  104. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/embeddings/wrapper.py +0 -0
  105. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/exceptions.py +0 -0
  106. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/__init__.py +0 -0
  107. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/aci.py +0 -0
  108. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ext/langchain.py +0 -0
  109. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/format_prompt.py +0 -0
  110. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/mcp.py +0 -0
  111. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/messages.py +0 -0
  112. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/__init__.py +0 -0
  113. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/anthropic.py +0 -0
  114. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/bedrock.py +0 -0
  115. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/cerebras.py +0 -0
  116. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/cohere.py +0 -0
  117. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/fallback.py +0 -0
  118. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/function.py +0 -0
  119. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/gemini.py +0 -0
  120. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/google.py +0 -0
  121. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/groq.py +0 -0
  122. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/huggingface.py +0 -0
  123. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/instrumented.py +0 -0
  124. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/mcp_sampling.py +0 -0
  125. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/mistral.py +0 -0
  126. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/openai.py +0 -0
  127. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/openrouter.py +0 -0
  128. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/outlines.py +0 -0
  129. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/test.py +0 -0
  130. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/wrapper.py +0 -0
  131. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/models/xai.py +0 -0
  132. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/output.py +0 -0
  133. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/__init__.py +0 -0
  134. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/amazon.py +0 -0
  135. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/anthropic.py +0 -0
  136. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/cohere.py +0 -0
  137. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/deepseek.py +0 -0
  138. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/google.py +0 -0
  139. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/grok.py +0 -0
  140. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/groq.py +0 -0
  141. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/harmony.py +0 -0
  142. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/meta.py +0 -0
  143. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/mistral.py +0 -0
  144. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/moonshotai.py +0 -0
  145. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/openai.py +0 -0
  146. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/qwen.py +0 -0
  147. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/profiles/zai.py +0 -0
  148. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/__init__.py +0 -0
  149. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/alibaba.py +0 -0
  150. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/anthropic.py +0 -0
  151. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/azure.py +0 -0
  152. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/bedrock.py +0 -0
  153. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/cerebras.py +0 -0
  154. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/cohere.py +0 -0
  155. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/deepseek.py +0 -0
  156. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/fireworks.py +0 -0
  157. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/gateway.py +0 -0
  158. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/github.py +0 -0
  159. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google.py +0 -0
  160. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google_gla.py +0 -0
  161. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/google_vertex.py +0 -0
  162. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/grok.py +0 -0
  163. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/groq.py +0 -0
  164. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/heroku.py +0 -0
  165. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/huggingface.py +0 -0
  166. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/litellm.py +0 -0
  167. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/mistral.py +0 -0
  168. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/moonshotai.py +0 -0
  169. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/nebius.py +0 -0
  170. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/ollama.py +0 -0
  171. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/openai.py +0 -0
  172. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/openrouter.py +0 -0
  173. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/outlines.py +0 -0
  174. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/ovhcloud.py +0 -0
  175. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/sambanova.py +0 -0
  176. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/sentence_transformers.py +0 -0
  177. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/together.py +0 -0
  178. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/vercel.py +0 -0
  179. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/voyageai.py +0 -0
  180. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/providers/xai.py +0 -0
  181. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/py.typed +0 -0
  182. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/result.py +0 -0
  183. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/retries.py +0 -0
  184. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/run.py +0 -0
  185. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/settings.py +0 -0
  186. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/tools.py +0 -0
  187. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/__init__.py +0 -0
  188. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/_dynamic.py +0 -0
  189. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/abstract.py +0 -0
  190. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/approval_required.py +0 -0
  191. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/combined.py +0 -0
  192. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/external.py +0 -0
  193. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/fastmcp.py +0 -0
  194. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/filtered.py +0 -0
  195. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/function.py +0 -0
  196. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/prefixed.py +0 -0
  197. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/prepared.py +0 -0
  198. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/renamed.py +0 -0
  199. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/toolsets/wrapper.py +0 -0
  200. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/__init__.py +0 -0
  201. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_adapter.py +0 -0
  202. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_event_stream.py +0 -0
  203. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_messages_builder.py +0 -0
  204. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/__init__.py +0 -0
  205. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/api.py +0 -0
  206. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/_web/app.py +0 -0
  207. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/__init__.py +0 -0
  208. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_adapter.py +0 -0
  209. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_event_stream.py +0 -0
  210. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/ag_ui/app.py +0 -0
  211. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/__init__.py +0 -0
  212. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_adapter.py +0 -0
  213. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_event_stream.py +0 -0
  214. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_models.py +0 -0
  215. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_utils.py +0 -0
  216. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/request_types.py +0 -0
  217. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/response_types.py +0 -0
  218. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/pydantic_ai/usage.py +0 -0
  219. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/_vendor/vendor.txt +0 -0
  220. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/__init__.py +0 -0
  221. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/deserialize.py +0 -0
  222. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/invocation_handle.py +0 -0
  223. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/middleware.py +0 -0
  224. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/node_result.py +0 -0
  225. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/client/reply_dispatcher.py +0 -0
  226. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/exceptions.py +0 -0
  227. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/__init__.py +0 -0
  228. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/actions.py +0 -0
  229. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/envelope.py +0 -0
  230. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/payload.py +0 -0
  231. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/models/tool_context.py +0 -0
  232. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/nodes/node.py +0 -0
  233. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/__init__.py +0 -0
  234. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/__init__.py +0 -0
  235. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/anthropic.py +0 -0
  236. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/model_client.py +0 -0
  237. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/providers/pydantic_ai/openai.py +0 -0
  238. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/__init__.py +0 -0
  239. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/worker.py +0 -0
  240. {calfkit-0.2.4 → calfkit-0.2.6}/calfkit/worker/worker_config.py +0 -0
  241. {calfkit-0.2.4 → calfkit-0.2.6}/codecov.yml +0 -0
  242. {calfkit-0.2.4 → calfkit-0.2.6}/examples/__init__.py +0 -0
  243. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/agent_dispatcher.py +0 -0
  244. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/chat_node.py +0 -0
  245. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/chat_repl_cli.py +0 -0
  246. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/invoke_agent.py +0 -0
  247. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/router_node.py +0 -0
  248. {calfkit-0.2.4 → calfkit-0.2.6}/examples/deprecated/tool_nodes.py +0 -0
  249. {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/agent_service.py +0 -0
  250. {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/invoke.py +0 -0
  251. {calfkit-0.2.4 → calfkit-0.2.6}/examples/quickstart/weather_tool.py +0 -0
  252. {calfkit-0.2.4 → calfkit-0.2.6}/examples/rpc_worker.py +0 -0
  253. {calfkit-0.2.4 → calfkit-0.2.6}/release-please-config.json +0 -0
  254. {calfkit-0.2.4 → calfkit-0.2.6}/tests/__init__.py +0 -0
  255. {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/__init__.py +0 -0
  256. {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/test_agent_output_types.py +0 -0
  257. {calfkit-0.2.4 → calfkit-0.2.6}/tests/integration/test_agent_workers.py +0 -0
  258. {calfkit-0.2.4 → calfkit-0.2.6}/tests/test_concurrent_tool_calls.py +0 -0
  259. {calfkit-0.2.4 → calfkit-0.2.6}/tests/test_instructions.py +0 -0
  260. {calfkit-0.2.4 → calfkit-0.2.6}/tests/utils.py +0 -0
@@ -22,7 +22,7 @@ jobs:
22
22
  steps:
23
23
  - name: Run release-please
24
24
  id: release
25
- uses: googleapis/release-please-action@v4
25
+ uses: googleapis/release-please-action@v5
26
26
  with:
27
27
  token: ${{ secrets.GITHUB_TOKEN }}
28
28
 
@@ -58,7 +58,7 @@ jobs:
58
58
  uses: actions/checkout@v6
59
59
 
60
60
  - name: Dependency Review
61
- uses: actions/dependency-review-action@v4
61
+ uses: actions/dependency-review-action@v5
62
62
  with:
63
63
  fail-on-severity: high
64
64
  deny-licenses: GPL-3.0, AGPL-3.0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.6"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.6](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.5...v0.2.6) (2026-05-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * add gates to BaseNodeDef for pre-run event filtering ([#127](https://github.com/calf-ai/calfkit-sdk/issues/127)) ([9fbdbd0](https://github.com/calf-ai/calfkit-sdk/commit/9fbdbd072a4de4335c3774efa8775c0e8b43b2bd))
9
+
10
+ ## [0.2.5](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.4...v0.2.5) (2026-04-11)
11
+
12
+
13
+ ### Features
14
+
15
+ * add serializable node schemas and runtime tool overrides ([#121](https://github.com/calf-ai/calfkit-sdk/issues/121)) ([e464c08](https://github.com/calf-ai/calfkit-sdk/commit/e464c08e4f179e0cd622789a067718fdbd043c1f))
16
+
3
17
  ## [0.2.4](https://github.com/calf-ai/calfkit-sdk/compare/v0.2.3...v0.2.4) (2026-04-08)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calfkit
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Build AI workflows and agents as fully-distributed and event-driven microservices.
5
5
  Project-URL: Homepage, https://github.com/calf-ai/calf-sdk
6
6
  Project-URL: Repository, https://github.com/calf-ai/calf-sdk
@@ -316,6 +316,48 @@ result = await client.execute_node(
316
316
 
317
317
  <br>
318
318
 
319
+ ### Gating Node Invocations (Optional)
320
+
321
+ When multiple agents share an input topic (each with its own consumer group), every agent receives every message published to that topic. A **gate stack** lets a node decide whether to handle an inbound event *before* `run()` runs — avoiding wasted LLM tokens on messages addressed elsewhere.
322
+
323
+ Gates are predicates: `Callable[[SessionRunContext], bool | Awaitable[bool]]`. They stack with **AND semantics** in registration order and short-circuit on the first `False`, exception, or non-bool return. When any gate rejects, `run()` is skipped and the envelope is returned unchanged — the Kafka offset still commits.
324
+
325
+ **Constructor form** — good for shared, cross-cutting predicates passed in as values:
326
+
327
+ ```python
328
+ def is_scheduler_target(ctx) -> bool:
329
+ discord = ctx.deps.provided_deps.get("discord", {})
330
+ return discord.get("slash_target") == "scheduler"
331
+
332
+ scheduler = Agent(
333
+ "scheduler",
334
+ subscribe_topics="discord.thread.123",
335
+ model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
336
+ gates=[is_scheduler_target],
337
+ )
338
+ ```
339
+
340
+ **Decorator form** — good for node-specific gates defined inline:
341
+
342
+ ```python
343
+ scheduler = Agent("scheduler", subscribe_topics="discord.thread.123", model_client=...)
344
+
345
+ @scheduler.gate
346
+ def is_scheduler_target(ctx) -> bool:
347
+ discord = ctx.deps.provided_deps.get("discord", {})
348
+ return discord.get("slash_target") == "scheduler"
349
+ ```
350
+
351
+ Constructor and decorator forms can be combined; constructor gates run first.
352
+
353
+ **Idempotency requirement**: Kafka may redeliver an event before its offset commits, so gates may run more than once for the same logical message. Keep gate functions deterministic and side-effect-free.
354
+
355
+ **Failure behavior**: If a gate raises or returns a non-bool, the framework logs the failure and rejects the message (fail-safe). Place cheap fast-reject gates first to maximize short-circuit efficiency.
356
+
357
+ For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)` directly; the `@agent_tool` decorator doesn't expose `gates=` because tool topics are typically 1:1.
358
+
359
+ <br>
360
+
319
361
  ## Documentation
320
362
 
321
363
  Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
@@ -278,6 +278,48 @@ result = await client.execute_node(
278
278
 
279
279
  <br>
280
280
 
281
+ ### Gating Node Invocations (Optional)
282
+
283
+ When multiple agents share an input topic (each with its own consumer group), every agent receives every message published to that topic. A **gate stack** lets a node decide whether to handle an inbound event *before* `run()` runs — avoiding wasted LLM tokens on messages addressed elsewhere.
284
+
285
+ Gates are predicates: `Callable[[SessionRunContext], bool | Awaitable[bool]]`. They stack with **AND semantics** in registration order and short-circuit on the first `False`, exception, or non-bool return. When any gate rejects, `run()` is skipped and the envelope is returned unchanged — the Kafka offset still commits.
286
+
287
+ **Constructor form** — good for shared, cross-cutting predicates passed in as values:
288
+
289
+ ```python
290
+ def is_scheduler_target(ctx) -> bool:
291
+ discord = ctx.deps.provided_deps.get("discord", {})
292
+ return discord.get("slash_target") == "scheduler"
293
+
294
+ scheduler = Agent(
295
+ "scheduler",
296
+ subscribe_topics="discord.thread.123",
297
+ model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
298
+ gates=[is_scheduler_target],
299
+ )
300
+ ```
301
+
302
+ **Decorator form** — good for node-specific gates defined inline:
303
+
304
+ ```python
305
+ scheduler = Agent("scheduler", subscribe_topics="discord.thread.123", model_client=...)
306
+
307
+ @scheduler.gate
308
+ def is_scheduler_target(ctx) -> bool:
309
+ discord = ctx.deps.provided_deps.get("discord", {})
310
+ return discord.get("slash_target") == "scheduler"
311
+ ```
312
+
313
+ Constructor and decorator forms can be combined; constructor gates run first.
314
+
315
+ **Idempotency requirement**: Kafka may redeliver an event before its offset commits, so gates may run more than once for the same logical message. Keep gate functions deterministic and side-effect-free.
316
+
317
+ **Failure behavior**: If a gate raises or returns a non-bool, the framework logs the failure and rejects the message (fail-safe). Place cheap fast-reject gates first to maximize short-circuit efficiency.
318
+
319
+ For tool-node gating, pass `gates=[...]` to `ToolNodeDef.create_tool_node(...)` directly; the `@agent_tool` decorator doesn't expose `gates=` because tool topics are typically 1:1.
320
+
321
+ <br>
322
+
281
323
  ## Documentation
282
324
 
283
325
  Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
@@ -2,7 +2,7 @@ from importlib.metadata import version
2
2
 
3
3
  from calfkit.client import Client, InvocationHandle, NodeResult
4
4
  from calfkit.models import ToolContext
5
- from calfkit.nodes import Agent, BaseNodeDef, NodeDef, ToolNodeDef, agent_tool
5
+ from calfkit.nodes import Agent, BaseNodeDef, GateFunction, NodeDef, ToolNodeDef, agent_tool
6
6
  from calfkit.providers import AnthropicModelClient, OpenAIModelClient, OpenAIResponsesModelClient
7
7
  from calfkit.worker import Worker
8
8
 
@@ -18,6 +18,7 @@ __all__ = [
18
18
  # nodes
19
19
  "Agent",
20
20
  "BaseNodeDef",
21
+ "GateFunction",
21
22
  "NodeDef",
22
23
  "ToolNodeDef",
23
24
  "agent_tool",
@@ -20,6 +20,7 @@ from calfkit.models.session_context import (
20
20
  SessionRunContext,
21
21
  WorkflowState,
22
22
  )
23
+ from calfkit.models.state import OverridesState
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
@@ -111,6 +112,7 @@ class BaseClient:
111
112
  reply_topic: str,
112
113
  correlation_id: str,
113
114
  state: State,
115
+ overrides: OverridesState | None = None,
114
116
  run_args: Sequence[Any] | None = None,
115
117
  deps: dict[str, Any] | None = None,
116
118
  output_type: type[Any] = _UNSET,
@@ -136,7 +138,7 @@ class BaseClient:
136
138
  await self._connection.start()
137
139
 
138
140
  call_stack = CallFrameStack()
139
- call_stack.push(CallFrame(target_topic=topic, callback_topic=reply_topic, input_args=run_args))
141
+ call_stack.push(CallFrame(target_topic=topic, callback_topic=reply_topic, input_args=run_args, overrides=overrides))
140
142
 
141
143
  envelope = Envelope(
142
144
  internal_workflow_state=WorkflowState(call_stack=call_stack),
@@ -12,6 +12,8 @@ from calfkit.client.deserialize import _UNSET
12
12
  from calfkit.client.invocation_handle import InvocationHandle
13
13
  from calfkit.client.node_result import NodeResult
14
14
  from calfkit.models import State
15
+ from calfkit.models.node_schema import BaseToolNodeSchema
16
+ from calfkit.models.state import OverridesState
15
17
 
16
18
 
17
19
  class Client(BaseClient):
@@ -30,6 +32,7 @@ class Client(BaseClient):
30
32
  topic: str,
31
33
  *,
32
34
  output_type: type[OutputT],
35
+ tool_overrides: list[BaseToolNodeSchema] | None = ...,
33
36
  reply_topic: str | None = ...,
34
37
  correlation_id: str | None = ...,
35
38
  temp_instructions: str | None = ...,
@@ -44,6 +47,7 @@ class Client(BaseClient):
44
47
  user_prompt: str,
45
48
  topic: str,
46
49
  *,
50
+ tool_overrides: list[BaseToolNodeSchema] | None = ...,
47
51
  reply_topic: str | None = ...,
48
52
  correlation_id: str | None = ...,
49
53
  temp_instructions: str | None = ...,
@@ -57,6 +61,7 @@ class Client(BaseClient):
57
61
  user_prompt: str,
58
62
  topic: str,
59
63
  *,
64
+ tool_overrides: list[BaseToolNodeSchema] | None = None,
60
65
  output_type: type[Any] = _UNSET,
61
66
  reply_topic: str | None = None,
62
67
  correlation_id: str | None = None,
@@ -74,6 +79,7 @@ class Client(BaseClient):
74
79
  Args:
75
80
  user_prompt: The user message to send to the agent node.
76
81
  topic: The Kafka topic the target node subscribes to.
82
+ tool_overrides: Runtime agent tool overrides.
77
83
  output_type: The expected Python type for deserializing the agent's
78
84
  output. When omitted, auto-detection is used (``DataPart`` →
79
85
  ``TextPart`` fallback).
@@ -82,7 +88,7 @@ class Client(BaseClient):
82
88
  correlation_id: Unique identifier to correlate this request with its
83
89
  reply. Auto-generated (uuid7) when ``None``.
84
90
  temp_instructions: Optional system-level instructions injected into
85
- the user prompt as a one-shot override.
91
+ the user prompt as a temporary prompt addition.
86
92
  message_history: Prior conversation messages to include for
87
93
  multi-turn context. Defaults to an empty history.
88
94
  run_args: Positional arguments forwarded to the node's ``run()``
@@ -107,6 +113,7 @@ class Client(BaseClient):
107
113
  correlation_id=correlation_id,
108
114
  run_args=run_args,
109
115
  state=state,
116
+ overrides=OverridesState(override_agent_tools=tool_overrides) if tool_overrides is not None else None,
110
117
  deps=deps,
111
118
  output_type=output_type,
112
119
  )
@@ -118,6 +125,7 @@ class Client(BaseClient):
118
125
  topic: str,
119
126
  *,
120
127
  output_type: type[OutputT],
128
+ tool_overrides: list[BaseToolNodeSchema] | None = ...,
121
129
  reply_topic: str | None = ...,
122
130
  correlation_id: str | None = ...,
123
131
  temp_instructions: str | None = ...,
@@ -133,6 +141,7 @@ class Client(BaseClient):
133
141
  user_prompt: str,
134
142
  topic: str,
135
143
  *,
144
+ tool_overrides: list[BaseToolNodeSchema] | None = ...,
136
145
  reply_topic: str | None = ...,
137
146
  correlation_id: str | None = ...,
138
147
  temp_instructions: str | None = ...,
@@ -147,6 +156,7 @@ class Client(BaseClient):
147
156
  user_prompt: str,
148
157
  topic: str,
149
158
  *,
159
+ tool_overrides: list[BaseToolNodeSchema] | None = None,
150
160
  output_type: type[Any] = _UNSET,
151
161
  reply_topic: str | None = None,
152
162
  correlation_id: str | None = None,
@@ -168,6 +178,7 @@ class Client(BaseClient):
168
178
  Args:
169
179
  user_prompt: The user message to send to the agent node.
170
180
  topic: The Kafka topic the target node subscribes to.
181
+ tool_overrides: Runtime agent tool overrides.
171
182
  output_type: The expected Python type for deserializing the agent's
172
183
  output. When omitted, auto-detection is used.
173
184
  reply_topic: Topic the node should publish its reply to.
@@ -175,7 +186,7 @@ class Client(BaseClient):
175
186
  correlation_id: Unique identifier to correlate this request with its
176
187
  reply. Auto-generated (uuid7) when ``None``.
177
188
  temp_instructions: Optional system-level instructions injected into
178
- the user prompt as a one-shot override.
189
+ the user prompt as a temporary prompt addition.
179
190
  message_history: Prior conversation messages to include for
180
191
  multi-turn context. Defaults to an empty history.
181
192
  run_args: Positional arguments forwarded to the node's ``run()``
@@ -195,6 +206,7 @@ class Client(BaseClient):
195
206
  handle = await self.invoke_node(
196
207
  user_prompt,
197
208
  topic,
209
+ tool_overrides=tool_overrides,
198
210
  output_type=output_type,
199
211
  reply_topic=reply_topic,
200
212
  correlation_id=correlation_id,
@@ -0,0 +1,21 @@
1
+ from dataclasses import KW_ONLY, dataclass
2
+
3
+ from calfkit._vendor.pydantic_ai.tools import ToolDefinition
4
+
5
+
6
+ @dataclass
7
+ class BaseNodeSchema:
8
+ _: KW_ONLY
9
+ node_id: str
10
+ subscribe_topics: list[str]
11
+ publish_topic: str | None
12
+
13
+ def __post_init__(self) -> None:
14
+ if not isinstance(self.subscribe_topics, (list, tuple)):
15
+ self.subscribe_topics = [self.subscribe_topics]
16
+
17
+
18
+ @dataclass
19
+ class BaseToolNodeSchema(BaseNodeSchema):
20
+ _: KW_ONLY
21
+ tool_schema: ToolDefinition
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
7
7
 
8
8
  from calfkit._types import DepsT, StackItemT, StateT
9
9
  from calfkit.models.actions import _Call
10
- from calfkit.models.state import State
10
+ from calfkit.models.state import OverridesState, State
11
11
 
12
12
 
13
13
  @dataclass
@@ -36,6 +36,7 @@ class CallFrame:
36
36
  callback_topic: str # return address
37
37
  input_args: Sequence[Any] | None = field(default=None)
38
38
  frame_id: str = field(default_factory=lambda: uuid_utils.uuid7().hex)
39
+ overrides: OverridesState | None = field(default=None)
39
40
 
40
41
 
41
42
  CallFrameStack = Stack[CallFrame]
@@ -11,6 +11,7 @@ from calfkit._vendor.pydantic_ai.messages import (
11
11
  ToolCallPart,
12
12
  )
13
13
  from calfkit._vendor.pydantic_ai.tools import DeferredToolCallResult as ToolCallResult
14
+ from calfkit.models.node_schema import BaseToolNodeSchema
14
15
  from calfkit.models.payload import ContentPart
15
16
 
16
17
 
@@ -18,6 +19,13 @@ class BaseAgentActivityState(BaseModel):
18
19
  model_config = ConfigDict(extra="ignore")
19
20
 
20
21
 
22
+ class OverridesState(BaseAgentActivityState):
23
+ """State for storing any override objects"""
24
+
25
+ model_config = ConfigDict(extra="ignore")
26
+ override_agent_tools: list[BaseToolNodeSchema] | None
27
+
28
+
21
29
  class CoreMessageState(BaseAgentActivityState):
22
30
  """The state for committed messages and structured objects"""
23
31
 
@@ -90,6 +98,7 @@ class State(CoreMessageState, InFlightToolsState):
90
98
  default=None,
91
99
  description="Additional data that can be accessed programmatically by the application but is not sent to the LLM.", # noqa: E501
92
100
  )
101
+ overrides: OverridesState | None = None
93
102
 
94
103
 
95
104
  # class State(BaseModel):
@@ -1,5 +1,5 @@
1
1
  from calfkit.nodes.agent import Agent, BaseAgentNodeDef
2
- from calfkit.nodes.base import BaseNodeDef
2
+ from calfkit.nodes.base import BaseNodeDef, GateFunction
3
3
  from calfkit.nodes.node import NodeDef
4
4
  from calfkit.nodes.tool import BaseToolNodeDef, ToolNodeDef, agent_tool
5
5
 
@@ -8,6 +8,7 @@ __all__ = [
8
8
  "BaseAgentNodeDef",
9
9
  "BaseNodeDef",
10
10
  "BaseToolNodeDef",
11
+ "GateFunction",
11
12
  "NodeDef",
12
13
  "ToolNodeDef",
13
14
  "agent_tool",
@@ -11,9 +11,10 @@ from calfkit._vendor.pydantic_ai.tools import DeferredToolResults
11
11
  from calfkit._vendor.pydantic_ai.toolsets.external import ExternalToolset
12
12
  from calfkit.models import Call, DataPart, NodeResult, ReturnCall, State, TailCall, TextPart
13
13
  from calfkit.models.actions import Silent
14
+ from calfkit.models.node_schema import BaseToolNodeSchema
14
15
  from calfkit.models.session_context import SessionRunContext
15
16
  from calfkit.models.state import PendingToolBatch
16
- from calfkit.nodes.base import BaseNodeDef
17
+ from calfkit.nodes.base import BaseNodeDef, GateFunction
17
18
  from calfkit.nodes.tool import ToolNodeDef
18
19
  from calfkit.providers.pydantic_ai.model_client import PydanticModelClient
19
20
 
@@ -33,6 +34,7 @@ class BaseAgentNodeDef(
33
34
  system_prompt: str = "You are a helpful AI assistant.",
34
35
  subscribe_topics: str | list[str],
35
36
  publish_topic: str | None = None,
37
+ gates: list[GateFunction] | None = None,
36
38
  tools: list[ToolNodeDef] | None = None,
37
39
  model_client: PydanticModelClient,
38
40
  final_output_type: OutputSpec[AgentOutputT] = str, # type: ignore[assignment]
@@ -44,7 +46,10 @@ class BaseAgentNodeDef(
44
46
  self.sequential_only_mode = sequential_only_mode
45
47
  self._pending_batches: dict[str, PendingToolBatch] = dict()
46
48
 
47
- super().__init__(node_id=node_id, subscribe_topics=subscribe_topics, publish_topic=publish_topic)
49
+ if not isinstance(subscribe_topics, (list, tuple)):
50
+ subscribe_topics = [subscribe_topics]
51
+
52
+ super().__init__(node_id=node_id, subscribe_topics=subscribe_topics, publish_topic=publish_topic, gates=gates)
48
53
 
49
54
  self._agent_loop: InternalAgentLoop[dict[str, Any], AgentOutputT | DeferredToolRequests] = InternalAgentLoop(
50
55
  model_client, name=self.name, output_type=[final_output_type, DeferredToolRequests], deps_type=dict, instructions=system_prompt
@@ -64,7 +69,11 @@ class BaseAgentNodeDef(
64
69
  del self._pending_batches[ctx.deps.correlation_id]
65
70
 
66
71
  async def run(self, ctx: SessionRunContext) -> NodeResult[State]:
67
- tools_registry = {tool.tool_schema.name: tool for tool in self.tools} if self.tools else dict[str, ToolNodeDef]()
72
+ tools_registry = dict[str, BaseToolNodeSchema]()
73
+ if ctx.state.overrides is not None and ctx.state.overrides.override_agent_tools is not None:
74
+ tools_registry = {tool.tool_schema.name: tool for tool in ctx.state.overrides.override_agent_tools}
75
+ elif self.tools:
76
+ tools_registry = {tool.tool_schema.name: tool for tool in self.tools}
68
77
 
69
78
  logger.debug(
70
79
  "[%s] agent run entered node=%s pending_tool_calls=%d history_len=%d",
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import logging
3
3
  from abc import abstractmethod
4
+ from collections.abc import Awaitable, Callable
4
5
  from typing import Annotated, Any
5
6
 
6
7
  from faststream import Context
@@ -17,17 +18,27 @@ from calfkit.models import (
17
18
  TailCall,
18
19
  )
19
20
  from calfkit.models.envelope import Envelope
21
+ from calfkit.models.node_schema import BaseNodeSchema
20
22
  from calfkit.models.session_context import SessionRunContext
21
23
 
22
24
  logger = logging.getLogger(__name__)
23
25
 
24
26
 
27
+ GateFunction = Callable[[SessionRunContext], bool | Awaitable[bool]]
28
+ """A predicate evaluated in ``handler()`` before ``run()``. Sync or async; must return ``bool``.
29
+
30
+ Returning ``False`` (or raising, or returning a non-bool) skips ``run()`` and returns the
31
+ envelope unchanged. Gates stack with AND semantics in registration order and short-circuit
32
+ on the first rejection.
33
+ """
34
+
35
+
25
36
  # ---------------------------------------------------------------------------
26
37
  # Base node definition
27
38
  # ---------------------------------------------------------------------------
28
39
 
29
40
 
30
- class BaseNodeDef:
41
+ class BaseNodeDef(BaseNodeSchema):
31
42
  _run_accepts_input: bool
32
43
 
33
44
  def __init_subclass__(cls, **kwargs: Any) -> None:
@@ -39,18 +50,70 @@ class BaseNodeDef:
39
50
 
40
51
  def __init__(
41
52
  self,
42
- node_id: str,
43
53
  *,
44
- subscribe_topics: str | list[str],
45
- publish_topic: str | None,
46
- ):
47
- self._node_id = node_id
48
- if isinstance(subscribe_topics, str):
49
- self.subscribe_topics = [subscribe_topics]
50
- elif subscribe_topics is not None:
51
- self.subscribe_topics = list(subscribe_topics)
52
- self.publish_topic = publish_topic
53
- self._return_topic = f"{node_id}.private.return"
54
+ node_id: str,
55
+ subscribe_topics: list[str],
56
+ publish_topic: str | None = None,
57
+ gates: list[GateFunction] | None = None,
58
+ ) -> None:
59
+ """Initialize a node definition.
60
+
61
+ Args:
62
+ node_id: Unique identifier for the node.
63
+ subscribe_topics: One or more topics the node consumes from.
64
+ publish_topic: Optional default topic to publish results to.
65
+ gates: Optional list of predicates evaluated in ``handler()`` before
66
+ ``run()``. Stack with AND semantics in registration order;
67
+ short-circuits on the first ``False``, exception, or non-bool.
68
+ Returning anything other than ``True`` rejects the message:
69
+ ``run()`` is skipped and the envelope is returned unchanged.
70
+ """
71
+ super().__init__(
72
+ node_id=node_id,
73
+ subscribe_topics=subscribe_topics,
74
+ publish_topic=publish_topic,
75
+ )
76
+ self.gates: list[GateFunction] = list(gates) if gates else []
77
+
78
+ def gate(self, fn: GateFunction) -> GateFunction:
79
+ """Register a gate predicate. Usable as a decorator. Repeatable.
80
+
81
+ Multiple gates evaluate in registration order with AND semantics
82
+ (short-circuit on the first ``False``, exception, or non-bool return).
83
+ Returns ``fn`` unchanged so it remains callable.
84
+ """
85
+ self.gates.append(fn)
86
+ return fn
87
+
88
+ async def _evaluate_gates(self, ctx: SessionRunContext, correlation_id: str) -> bool:
89
+ for i, gate in enumerate(self.gates):
90
+ try:
91
+ result = gate(ctx)
92
+ if inspect.isawaitable(result):
93
+ result = await result
94
+ if not isinstance(result, bool):
95
+ raise TypeError(
96
+ f"gate[{i}] {getattr(gate, '__name__', repr(gate))} for node={self.node_id} returned {type(result).__name__}; expected bool"
97
+ )
98
+ if not result:
99
+ logger.debug(
100
+ "[%s] gate[%d]=%s rejected node=%s",
101
+ correlation_id[:8],
102
+ i,
103
+ getattr(gate, "__name__", "?"),
104
+ self.node_id,
105
+ )
106
+ return False
107
+ except Exception:
108
+ logger.exception(
109
+ "[%s] gate[%d]=%s raised for node=%s; treating as reject",
110
+ correlation_id[:8],
111
+ i,
112
+ getattr(gate, "__name__", "?"),
113
+ self.node_id,
114
+ )
115
+ return False
116
+ return True
54
117
 
55
118
  # TODO: consider multiple abstract methods per node based on the incoming communication pattern,
56
119
  # like a delgation or emit. So the communication-specific handler can properly handle it
@@ -77,6 +140,8 @@ class BaseNodeDef:
77
140
 
78
141
  async def prepare_context(self, envelope: Envelope) -> SessionRunContext:
79
142
  ctx = envelope.context.model_copy(deep=True)
143
+ if envelope.internal_workflow_state.current_frame.overrides:
144
+ ctx.state.overrides = envelope.internal_workflow_state.current_frame.overrides
80
145
  return ctx
81
146
 
82
147
  async def _publish_action(self, output: NodeResult[State], envelope: Envelope, correlation_id: str, broker: BrokerAnnotation) -> Envelope:
@@ -107,7 +172,7 @@ class BaseNodeDef:
107
172
  internal_workflow_state=envelope.internal_workflow_state,
108
173
  )
109
174
  target_topic = envelope.internal_workflow_state.current_frame.target_topic
110
- logger.debug("[%s] Call target=%s frame_pushed node=%s", correlation_id[:8], target_topic, self._node_id)
175
+ logger.debug("[%s] Call target=%s frame_pushed node=%s", correlation_id[:8], target_topic, self.node_id)
111
176
  await broker.publish(
112
177
  publish_envelope,
113
178
  topic=target_topic,
@@ -121,7 +186,7 @@ class BaseNodeDef:
121
186
  context=SessionRunContext(state=output.state, deps=envelope.context.deps),
122
187
  internal_workflow_state=envelope.internal_workflow_state,
123
188
  )
124
- logger.debug("[%s] ReturnCall callback=%s node=%s", correlation_id[:8], frame.callback_topic, self._node_id)
189
+ logger.debug("[%s] ReturnCall callback=%s node=%s", correlation_id[:8], frame.callback_topic, self.node_id)
125
190
  await broker.publish(
126
191
  publish_envelope,
127
192
  topic=frame.callback_topic,
@@ -138,7 +203,7 @@ class BaseNodeDef:
138
203
  internal_workflow_state=envelope.internal_workflow_state,
139
204
  )
140
205
  target_topic = envelope.internal_workflow_state.current_frame.target_topic
141
- logger.debug("[%s] TailCall target=%s node=%s", correlation_id[:8], target_topic, self._node_id)
206
+ logger.debug("[%s] TailCall target=%s node=%s", correlation_id[:8], target_topic, self.node_id)
142
207
  await broker.publish(
143
208
  publish_envelope,
144
209
  topic=target_topic,
@@ -164,21 +229,29 @@ class BaseNodeDef:
164
229
  correlation_id: Annotated[str, Context()],
165
230
  broker: BrokerAnnotation,
166
231
  ) -> Envelope:
167
- logger.debug("[%s] handler entered node=%s", correlation_id[:8], self._node_id)
232
+ logger.debug("[%s] handler entered node=%s", correlation_id[:8], self.node_id)
168
233
  ctx = await self.prepare_context(envelope)
234
+
235
+ if not await self._evaluate_gates(ctx, correlation_id):
236
+ return envelope
237
+
169
238
  if self._run_accepts_input and envelope.internal_workflow_state.current_frame.input_args is not None:
170
239
  output = await self.run(ctx, *envelope.internal_workflow_state.current_frame.input_args)
171
240
  else:
172
241
  output = await self.run(ctx)
173
242
 
174
- logger.debug("[%s] run() returned action=%s node=%s", correlation_id[:8], type(output).__name__, self._node_id)
243
+ logger.debug("[%s] run() returned action=%s node=%s", correlation_id[:8], type(output).__name__, self.node_id)
175
244
 
176
245
  return await self._publish_action(output, envelope, correlation_id, broker)
177
246
 
178
247
  @property
179
248
  def id(self) -> str:
180
- return self._node_id
249
+ return self.node_id
181
250
 
182
251
  @property
183
252
  def name(self) -> str:
184
- return self._node_id
253
+ return self.node_id
254
+
255
+ @property
256
+ def _return_topic(self) -> str:
257
+ return f"{self.node_id}.private.return"