agent-workflow-sdk 0.2.0__tar.gz → 0.4.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 (172) hide show
  1. agent_workflow_sdk-0.4.0/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  2. agent_workflow_sdk-0.4.0/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  3. agent_workflow_sdk-0.4.0/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  4. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/.github/workflows/ci.yml +36 -0
  5. agent_workflow_sdk-0.4.0/.github/workflows/docs.yml +54 -0
  6. agent_workflow_sdk-0.4.0/.github/workflows/release.yml +88 -0
  7. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/CHANGELOG.md +69 -2
  8. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/DESIGN.md +17 -7
  9. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/PKG-INFO +114 -42
  10. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/README.md +111 -43
  11. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/__init__.py +2 -2
  12. agent_workflow_sdk-0.4.0/agentflow/_pg.py +79 -0
  13. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/_serde.py +2 -0
  14. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/base.py +21 -0
  15. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/postgres.py +27 -14
  16. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/compiled.py +46 -0
  17. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/memory.py +14 -1
  18. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/postgres.py +54 -27
  19. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/queue.py +10 -5
  20. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/records.py +11 -3
  21. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/worker.py +13 -10
  22. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/errors.py +8 -3
  23. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/__init__.py +12 -0
  24. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/effects.py +1 -3
  25. agent_workflow_sdk-0.4.0/agentflow/prebuilt/watch.py +238 -0
  26. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/runtime.py +27 -2
  27. agent_workflow_sdk-0.4.0/agentflow/server/__init__.py +46 -0
  28. agent_workflow_sdk-0.4.0/agentflow/server/app.py +205 -0
  29. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/state.py +1 -1
  30. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/store/postgres.py +31 -18
  31. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/control-plane.md +62 -1
  32. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/patterns/long-running-loops.md +39 -0
  33. agent_workflow_sdk-0.4.0/docs/reference/api/server.md +23 -0
  34. agent_workflow_sdk-0.4.0/docs/reference/api/watch.md +12 -0
  35. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/research_plan_implement_review.py +17 -6
  36. agent_workflow_sdk-0.4.0/examples/serve_control_plane.py +89 -0
  37. agent_workflow_sdk-0.4.0/examples/watch_until_done.py +101 -0
  38. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/mkdocs.yml +2 -0
  39. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/pyproject.toml +10 -5
  40. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/conftest.py +30 -0
  41. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_gate.py +1 -3
  42. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_idempotent.py +2 -6
  43. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_otel.py +4 -2
  44. agent_workflow_sdk-0.4.0/tests/test_postgres_checkpointer.py +167 -0
  45. agent_workflow_sdk-0.4.0/tests/test_postgres_runqueue.py +324 -0
  46. agent_workflow_sdk-0.4.0/tests/test_postgres_store.py +169 -0
  47. agent_workflow_sdk-0.4.0/tests/test_server.py +172 -0
  48. agent_workflow_sdk-0.4.0/tests/test_watch.py +213 -0
  49. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/.env.example +0 -0
  50. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/.gitignore +0 -0
  51. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/CONTRIBUTING.md +0 -0
  52. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/LICENSE +0 -0
  53. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/NOTICE +0 -0
  54. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/SECURITY.md +0 -0
  55. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/__init__.py +0 -0
  56. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/_http.py +0 -0
  57. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/anthropic.py +0 -0
  58. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/base.py +0 -0
  59. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/claude_code.py +0 -0
  60. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/cli_exec.py +0 -0
  61. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/codex.py +0 -0
  62. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/kiro.py +0 -0
  63. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/ollama.py +0 -0
  64. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/openai.py +0 -0
  65. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/__init__.py +0 -0
  66. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/file.py +0 -0
  67. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/memory.py +0 -0
  68. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/redis.py +0 -0
  69. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/sqlite.py +0 -0
  70. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/__init__.py +0 -0
  71. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/registry.py +0 -0
  72. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/events.py +0 -0
  73. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/graph.py +0 -0
  74. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/interrupts.py +0 -0
  75. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/observability.py +0 -0
  76. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/otel.py +0 -0
  77. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/gate.py +0 -0
  78. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/idempotent.py +0 -0
  79. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/loop.py +0 -0
  80. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/resilience.py +0 -0
  81. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/tool_loop.py +0 -0
  82. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/prometheus.py +0 -0
  83. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/py.typed +0 -0
  84. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/redaction.py +0 -0
  85. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/store/__init__.py +0 -0
  86. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/store/_util.py +0 -0
  87. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/store/base.py +0 -0
  88. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/store/memory.py +0 -0
  89. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/agentflow/telemetry.py +0 -0
  90. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/backends/agent-backends.md +0 -0
  91. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/backends/llm-backends.md +0 -0
  92. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/backends/overview.md +0 -0
  93. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/backends/permissions.md +0 -0
  94. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/backends/retries.md +0 -0
  95. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/context.md +0 -0
  96. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/execution.md +0 -0
  97. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/nodes-and-edges.md +0 -0
  98. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/running.md +0 -0
  99. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/state.md +0 -0
  100. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/concepts/subgraphs.md +0 -0
  101. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/checkpointing.md +0 -0
  102. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/human-in-the-loop.md +0 -0
  103. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/observability.md +0 -0
  104. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/quality-gates.md +0 -0
  105. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/durability/store.md +0 -0
  106. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/get-started/installation.md +0 -0
  107. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/get-started/mental-model.md +0 -0
  108. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/get-started/overview.md +0 -0
  109. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/get-started/quickstart.md +0 -0
  110. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/index.md +0 -0
  111. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/patterns/prebuilt.md +0 -0
  112. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/patterns/tutorial-qa-agent.md +0 -0
  113. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/backends.md +0 -0
  114. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/checkpoint.md +0 -0
  115. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/compiled.md +0 -0
  116. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/controlplane.md +0 -0
  117. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/errors.md +0 -0
  118. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/events.md +0 -0
  119. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/gate.md +0 -0
  120. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/graph.md +0 -0
  121. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/interrupts.md +0 -0
  122. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/observability.md +0 -0
  123. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/permissions.md +0 -0
  124. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/prebuilt.md +0 -0
  125. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/runtime.md +0 -0
  126. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/state.md +0 -0
  127. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/store.md +0 -0
  128. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/api.md +0 -0
  129. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/docs/reference/glossary.md +0 -0
  130. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/agents_demo.py +0 -0
  131. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/codebase_qa_ollama.py +0 -0
  132. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/hello_graph.py +0 -0
  133. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/hitl_permission.py +0 -0
  134. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/idempotent_effect.py +0 -0
  135. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/mixed_backends.py +0 -0
  136. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/optimize_loop.py +0 -0
  137. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/quality_gate.py +0 -0
  138. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/telemetry_demo.py +0 -0
  139. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/examples/tool_loop_ollama.py +0 -0
  140. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_anthropic_backend.py +0 -0
  141. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_backend_lifecycle.py +0 -0
  142. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_checkpoint_contract.py +0 -0
  143. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_cli_backends.py +0 -0
  144. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_concurrency.py +0 -0
  145. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_controlplane.py +0 -0
  146. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_effects.py +0 -0
  147. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_graph_engine.py +0 -0
  148. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_hitl_permission.py +0 -0
  149. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_http_retry.py +0 -0
  150. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_input_validation.py +0 -0
  151. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_kiro_backend.py +0 -0
  152. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_live_backends.py +0 -0
  153. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_observability.py +0 -0
  154. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_ollama_backend.py +0 -0
  155. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_openai_backend.py +0 -0
  156. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_permission_policy.py +0 -0
  157. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_prebuilt_loop.py +0 -0
  158. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_prebuilt_no_httpx.py +0 -0
  159. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_prometheus.py +0 -0
  160. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_provider_resilience.py +0 -0
  161. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_redaction.py +0 -0
  162. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_redis_checkpointer.py +0 -0
  163. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_resilience.py +0 -0
  164. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_run_timeout.py +0 -0
  165. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_sqlite_checkpointer.py +0 -0
  166. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_state_isolation.py +0 -0
  167. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_step_atomicity.py +0 -0
  168. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_store.py +0 -0
  169. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_subgraph.py +0 -0
  170. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_sync_facade.py +0 -0
  171. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_telemetry.py +0 -0
  172. {agent_workflow_sdk-0.2.0 → agent_workflow_sdk-0.4.0}/tests/test_tool_loop.py +0 -0
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something behaves incorrectly or crashes
4
+ labels: bug
5
+ ---
6
+
7
+ ## What happened
8
+
9
+ A clear description of the bug and what you expected instead.
10
+
11
+ ## Reproduction
12
+
13
+ Smallest graph/code that reproduces it. A runnable snippet is ideal.
14
+
15
+ ```python
16
+ # ...
17
+ ```
18
+
19
+ ## Environment
20
+
21
+ - `agent-workflow-sdk` version:
22
+ - Python version:
23
+ - Backend involved (KiroBackend / CodexBackend / ClaudeCodeBackend / OllamaBackend / OpenAIBackend / AnthropicBackend / none):
24
+ - Checkpointer / Store / control-plane backend, if relevant (memory / file / sqlite / redis / postgres):
25
+
26
+ ## Logs or traceback
27
+
28
+ ```
29
+ paste here
30
+ ```
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a capability or change
4
+ labels: enhancement
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ What are you trying to do that the SDK makes hard or impossible today?
10
+
11
+ ## Proposed solution
12
+
13
+ What you'd like to see. If it touches the public API, sketch the shape
14
+ (a node, a backend, a prebuilt pattern, a control-plane method, ...).
15
+
16
+ ## Alternatives considered
17
+
18
+ Workarounds you've tried, or other designs you weighed.
19
+
20
+ ## Scope check
21
+
22
+ Does this fit a library (engine, backends, control plane) rather than a
23
+ service layer? See `DESIGN.md` for what is intentionally out of scope.
@@ -0,0 +1,17 @@
1
+ ## What and why
2
+
3
+ What this changes and the problem it solves. Link any related issue.
4
+
5
+ ## How
6
+
7
+ Key implementation points a reviewer should know. Note any new public API
8
+ or any change to a documented contract in `DESIGN.md`.
9
+
10
+ ## Checklist
11
+
12
+ - [ ] `ruff check agentflow tests examples` passes
13
+ - [ ] `ruff format --check agentflow tests examples` passes
14
+ - [ ] `mypy agentflow` passes
15
+ - [ ] `pytest -q` (offline suite) passes; added/updated tests for the change
16
+ - [ ] Docs updated if behavior or public API changed (README / `docs/` / docstrings)
17
+ - [ ] `CHANGELOG.md` updated under `[Unreleased]` for a user-facing change
@@ -78,3 +78,39 @@ jobs:
78
78
  assert "agentflow/py.typed" in names, "py.typed missing from wheel"
79
79
  print("py.typed present in", wheel)
80
80
  PY
81
+
82
+ pg-tests:
83
+ # The Postgres backends (Store, Checkpointer, RunQueue) are skipped by the
84
+ # hermetic default run, so their real SQL paths are exercised here against
85
+ # a live server started as a service container.
86
+ runs-on: ubuntu-latest
87
+ services:
88
+ postgres:
89
+ image: postgres:16
90
+ env:
91
+ POSTGRES_USER: postgres
92
+ POSTGRES_PASSWORD: postgres
93
+ POSTGRES_DB: agentflow_test
94
+ ports:
95
+ - 5432:5432
96
+ options: >-
97
+ --health-cmd pg_isready
98
+ --health-interval 5s
99
+ --health-timeout 5s
100
+ --health-retries 10
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ - uses: actions/setup-python@v5
104
+ with:
105
+ python-version: "3.11"
106
+ - name: Install (postgres + ollama + dev extras)
107
+ # ollama is included for httpx: pytest imports every test module during
108
+ # collection, and the HTTP-backend test modules import httpx at top
109
+ # level — without it, collection errors before the -m pg filter applies.
110
+ run: |
111
+ python -m pip install --upgrade pip
112
+ pip install -e '.[postgres,ollama,dev]'
113
+ - name: Run the Postgres test suite
114
+ env:
115
+ POSTGRES_TEST_DSN: postgresql://postgres:postgres@localhost:5432/agentflow_test
116
+ run: pytest -q -m pg tests/test_postgres_store.py tests/test_postgres_checkpointer.py tests/test_postgres_runqueue.py
@@ -0,0 +1,54 @@
1
+ name: Docs
2
+
3
+ # Build the mkdocs site and deploy it to GitHub Pages when docs change on main,
4
+ # or on demand from the Actions tab. Triggering on main (not a tag) matches the
5
+ # github-pages environment's branch protection, which allows the default branch
6
+ # — a tag push is rejected by that rule. Enable Pages once
7
+ # (Settings -> Pages -> Source: GitHub Actions) for the deploy step to publish.
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ paths:
12
+ - "docs/**"
13
+ - "mkdocs.yml"
14
+ - "agentflow/**"
15
+ - ".github/workflows/docs.yml"
16
+ workflow_dispatch:
17
+
18
+ # Allow one concurrent deploy; a newer run supersedes an in-flight one.
19
+ concurrency:
20
+ group: pages
21
+ cancel-in-progress: true
22
+
23
+ jobs:
24
+ build:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.11"
31
+ - name: Install docs extra
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ pip install -e '.[docs]'
35
+ - name: Build the site (strict)
36
+ run: mkdocs build --strict
37
+ - name: Upload the Pages artifact
38
+ uses: actions/upload-pages-artifact@v3
39
+ with:
40
+ path: site
41
+
42
+ deploy:
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ permissions:
46
+ pages: write
47
+ id-token: write
48
+ environment:
49
+ name: github-pages
50
+ url: ${{ steps.deployment.outputs.page_url }}
51
+ steps:
52
+ - name: Deploy to GitHub Pages
53
+ id: deployment
54
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,88 @@
1
+ name: Release
2
+
3
+ # Publish to PyPI and cut a GitHub Release when a version tag is pushed.
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.11"
17
+
18
+ - name: Verify the tag matches the packaged version
19
+ # A tag of vX.Y.Z must match pyproject's version and __version__, so a
20
+ # mistagged release fails here instead of publishing the wrong build.
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ tag="${GITHUB_REF_NAME#v}"
24
+ proj=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
25
+ pkg=$(python -c "import re; print(re.search(r'__version__ = \"([^\"]+)\"', open('agentflow/__init__.py').read()).group(1))")
26
+ echo "tag=$tag pyproject=$proj package=$pkg"
27
+ test "$tag" = "$proj" || { echo "tag != pyproject version"; exit 1; }
28
+ test "$tag" = "$pkg" || { echo "tag != __version__"; exit 1; }
29
+
30
+ - name: Build wheel + sdist
31
+ run: |
32
+ python -m pip install build
33
+ python -m build
34
+
35
+ - name: Check artifacts (twine + py.typed)
36
+ run: |
37
+ python -m pip install twine
38
+ python -m twine check dist/*
39
+ python - <<'PY'
40
+ import glob, zipfile
41
+ wheel = glob.glob("dist/*.whl")[0]
42
+ assert "agentflow/py.typed" in zipfile.ZipFile(wheel).namelist(), "py.typed missing"
43
+ print("py.typed present in", wheel)
44
+ PY
45
+
46
+ - name: Upload built artifacts
47
+ uses: actions/upload-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+
52
+ publish-pypi:
53
+ needs: build
54
+ runs-on: ubuntu-latest
55
+ # Trusted Publishing (OIDC): no API token stored anywhere. Configure the
56
+ # matching publisher for this repo/workflow on PyPI once (Project settings
57
+ # -> Publishing) and every tagged build publishes with no secret.
58
+ environment: pypi
59
+ permissions:
60
+ id-token: write
61
+ steps:
62
+ - name: Fetch built artifacts
63
+ uses: actions/download-artifact@v4
64
+ with:
65
+ name: dist
66
+ path: dist/
67
+ - name: Publish to PyPI
68
+ uses: pypa/gh-action-pypi-publish@release/v1
69
+
70
+ github-release:
71
+ needs: publish-pypi
72
+ runs-on: ubuntu-latest
73
+ permissions:
74
+ contents: write
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+ - name: Fetch built artifacts
78
+ uses: actions/download-artifact@v4
79
+ with:
80
+ name: dist
81
+ path: dist/
82
+ - name: Create the GitHub Release
83
+ env:
84
+ GH_TOKEN: ${{ github.token }}
85
+ run: |
86
+ gh release create "$GITHUB_REF_NAME" dist/* \
87
+ --title "$GITHUB_REF_NAME" \
88
+ --notes "See [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/main/CHANGELOG.md) for what changed."
@@ -4,6 +4,73 @@ All notable changes to `agent-workflow-sdk` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  project aims to follow [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.4.0] - 2026-10-03
8
+
9
+ ### Added
10
+
11
+ - Optional HTTP control-plane server (`server` extra): `agentflow.server.
12
+ create_app(queue)` returns a FastAPI ASGI app that maps 1:1 onto a
13
+ `RunQueue`, so runs can be enqueued and observed over the network. Routes:
14
+ `POST /runs`, `GET /runs/{id}`, `GET /runs`, `POST /runs/{id}/resume`,
15
+ `POST /runs/{id}/cancel`, `GET /stats`, `GET /healthz`. Request/response
16
+ bodies are Pydantic models, so FastAPI serves interactive OpenAPI docs at
17
+ `/docs`. `pool_health=` wires a `WorkerPool`'s health into `/healthz`. The
18
+ app adds no behavior of its own — each route forwards to one queue method.
19
+ See `examples/serve_control_plane.py` and the control-plane docs.
20
+ - `server` install extra: `pip install 'agent-workflow-sdk[server]'`
21
+ (`fastapi>=0.110`). The core remains dependency-free; serve the returned
22
+ ASGI app with any ASGI server (e.g. `uvicorn`), installed separately.
23
+
24
+ ### Changed
25
+
26
+ - The worker protocol (`claim` / `heartbeat` / `complete`) is deliberately not
27
+ exposed over HTTP; workers talk to the queue directly. The HTTP layer ships
28
+ no authentication, authorization, rate limiting, or multi-tenancy — those
29
+ stay out of scope and are the caller's responsibility (trust boundary /
30
+ reverse proxy). `DESIGN.md` updated accordingly.
31
+
32
+ ## [0.3.0] - 2026-10-02
33
+
34
+ ### Added
35
+
36
+ - Watch nodes: park a run on an external system without burning turns while
37
+ nothing changes. `ctx.wait(wake_at, payload)` is a timer-based sibling of the
38
+ human interrupt — it suspends the run until a time, then re-runs the node.
39
+ `Checkpoint` gains `wake_at` and a derived `suspend_kind` (`"human"` |
40
+ `"timer"`); the human-interrupt path is unchanged.
41
+ - Durable parked execution: `RunStatus.WAITING` + `RunRecord.wake_at`. A run
42
+ parked on `ctx.wait` is `WAITING` in the queue and does not hold a worker;
43
+ `claim` re-takes it once `wake_at` passes (symmetric to lease expiry), in both
44
+ `MemoryRunQueue` and `PostgresRunQueue`. The worker frees itself on a timer
45
+ suspend and resumes on re-claim.
46
+ - `CompiledGraph.run_until_done(...)` drives timer waits inline (sleeps until
47
+ `wake_at` and resumes), for running a watch loop without the control plane —
48
+ the process must stay alive for the wait, unlike the parked control-plane
49
+ path.
50
+ - `agentflow.prebuilt.watch`: `WatchResult` (idle/activity/terminal), the
51
+ `Watcher` protocol, `CommandWatcher` (poll any command that prints the
52
+ contract JSON), `watch_node` (polls, parks on idle, loops until non-idle), and
53
+ `route_watch`. GitHub/CI/etc. are examples over `CommandWatcher`, not core.
54
+ See `examples/watch_until_done.py`.
55
+
56
+ ### Fixed
57
+
58
+ - First-use schema creation on PostgreSQL is now concurrency-safe. `CREATE TABLE
59
+ IF NOT EXISTS` can collide on `pg_type` when two sessions bootstrap the same
60
+ table at once; the Postgres backends now run that DDL under a
61
+ `pg_advisory_xact_lock` (plus an in-process lock), so a pool of workers
62
+ starting together no longer races.
63
+
64
+ ### Changed
65
+
66
+ - Internal: the Postgres backends (`PostgresStore`, `PostgresCheckpointer`,
67
+ `PostgresRunQueue`) now have a real-server contract test suite, run in CI
68
+ against a `postgres` service container. The tests are marked `pg` and skipped
69
+ by the default hermetic run (they need `POSTGRES_TEST_DSN`); they cover the
70
+ SQL-only paths the in-memory suites can't — the `if_absent` conditional write
71
+ and `StoreConflict`, the `if_revision` compare-and-set and `CheckpointConflict`,
72
+ and concurrent `claim` under `FOR UPDATE SKIP LOCKED`.
73
+
7
74
  ## [0.2.0] - 2026-10-01
8
75
 
9
76
  ### Added
@@ -99,8 +166,8 @@ project aims to follow [Semantic Versioning](https://semver.org/).
99
166
 
100
167
  ## [0.1.0] - 2026-09-29
101
168
 
102
- First public release: an async-first, LangGraph-style SDK for agent workflows
103
- with pluggable agent and LLM backends.
169
+ First public release: an async-first SDK for agent workflows with pluggable
170
+ agent and LLM backends.
104
171
 
105
172
  ### Added
106
173
 
@@ -152,9 +152,11 @@ execution state; a store is cross-thread application data.
152
152
 
153
153
  ## Control plane
154
154
 
155
- The control plane manages runs from outside the process that created them. It
156
- is a library layer, not a service — there is no HTTP server (that is planned as
157
- a thin adapter over these methods).
155
+ The control plane manages runs from outside the process that created them. The
156
+ core is a library layer, not a service. An optional HTTP layer — the `server`
157
+ extra, `agentflow.server.create_app` — is a thin FastAPI adapter mapping 1:1
158
+ over the queue methods below; it adds no behavior of its own, and auth and
159
+ multi-tenancy remain out of scope (see below).
158
160
 
159
161
  - `GraphRegistry` maps a graph name to a compiled-graph factory. Graphs are
160
162
  code and cannot be serialized, so a run request references a graph by name;
@@ -172,6 +174,13 @@ a thin adapter over these methods).
172
174
  concurrency.
173
175
  - Monitoring: `RunQueue.stats()` and `WorkerPool.health()` return plain values
174
176
  a caller can expose through its own `/healthz` and `/metrics`.
177
+ - HTTP (optional, `server` extra): `agentflow.server.create_app(queue)` returns
178
+ a FastAPI ASGI app exposing the enqueue-and-observe plane — `POST /runs`,
179
+ `GET /runs/{id}`, `GET /runs`, `POST /runs/{id}/resume`,
180
+ `POST /runs/{id}/cancel`, `GET /stats`, `GET /healthz`. The worker protocol
181
+ (`claim` / `heartbeat` / `complete`) is deliberately not exposed; workers
182
+ talk to the queue directly. Each route forwards to one queue method, so the
183
+ HTTP layer carries no state of its own.
175
184
 
176
185
  ## Observability
177
186
 
@@ -197,10 +206,11 @@ See `SECURITY.md` for the reporting process and the operator-facing summary.
197
206
 
198
207
  ## What is intentionally out of scope
199
208
 
200
- - An HTTP/gRPC API over the control plane, and the auth/multi-tenancy that a
201
- service layer implies. These are properties of a service, not this SDK, and
202
- are deferred to a future `server` layer that adapts the existing queue
203
- methods.
209
+ - Authentication, authorization, and multi-tenancy over the HTTP layer. The
210
+ optional `server` extra adapts the queue to HTTP, but it ships no auth and no
211
+ per-tenant isolation — those are properties of a service, not this SDK. Run
212
+ it inside your own trust boundary, or behind a reverse proxy that supplies
213
+ them. A gRPC API is not provided.
204
214
  - Rate limiting and per-tenant quotas. `max_concurrency` /
205
215
  `max_node_concurrency` bound pressure within one process; per-tenant
206
216
  isolation is the caller's responsibility until the service layer exists.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-workflow-sdk
3
- Version: 0.2.0
4
- Summary: Async-first, LangGraph-style SDK for building agent workflows with pluggable agent and LLM backends.
3
+ Version: 0.4.0
4
+ Summary: Async-first SDK for orchestrating local coding-agent CLIs (Claude Code, Codex, Kiro) and LLMs as durable, graph-based workflows.
5
5
  Project-URL: Homepage, https://github.com/dankosorkin/agent-workflow-sdk
6
6
  Project-URL: Repository, https://github.com/dankosorkin/agent-workflow-sdk
7
7
  Project-URL: Changelog, https://github.com/dankosorkin/agent-workflow-sdk/blob/main/CHANGELOG.md
@@ -20,6 +20,8 @@ Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.11
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: fakeredis>=2.20; extra == 'dev'
23
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
24
+ Requires-Dist: httpx>=0.27; extra == 'dev'
23
25
  Requires-Dist: mypy>=1.11; extra == 'dev'
24
26
  Requires-Dist: pip-audit>=2.7; extra == 'dev'
25
27
  Requires-Dist: prometheus-client>=0.20; extra == 'dev'
@@ -41,73 +43,143 @@ Provides-Extra: prometheus
41
43
  Requires-Dist: prometheus-client>=0.20; extra == 'prometheus'
42
44
  Provides-Extra: redis
43
45
  Requires-Dist: redis>=5; extra == 'redis'
46
+ Provides-Extra: server
47
+ Requires-Dist: fastapi>=0.110; extra == 'server'
44
48
  Description-Content-Type: text/markdown
45
49
 
46
50
  # agent-workflow-sdk
47
51
 
48
- An async-first, LangGraph-style SDK for building agent workflows from
49
- composable pieces. You describe a workflow as a graph of nodes over a typed,
50
- reducer-based state, and run it against a pluggable backend — a coding agent
51
- (Kiro, Codex, or Claude Code) or a plain LLM (Ollama, any OpenAI-compatible
52
- endpoint, or Anthropic).
52
+ [![PyPI](https://img.shields.io/pypi/v/agent-workflow-sdk)](https://pypi.org/project/agent-workflow-sdk/)
53
+ [![Python](https://img.shields.io/pypi/pyversions/agent-workflow-sdk)](https://pypi.org/project/agent-workflow-sdk/)
54
+ [![CI](https://github.com/dankosorkin/agent-workflow-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/dankosorkin/agent-workflow-sdk/actions/workflows/ci.yml)
55
+ [![License](https://img.shields.io/pypi/l/agent-workflow-sdk)](LICENSE)
56
+ [![Docs](https://img.shields.io/badge/docs-live-brightgreen)](https://dankosorkin.github.io/agent-workflow-sdk/)
53
57
 
54
- The import root is `agentflow`. The distribution name is
55
- `agent-workflow-sdk`.
58
+ An async-first SDK for building agent workflows from composable pieces. You
59
+ describe a workflow as a graph of nodes over a typed, reducer-based state, and
60
+ run it against a pluggable backend — a local coding agent (Claude Code, Codex,
61
+ or Kiro) or a plain LLM (Ollama, any OpenAI-compatible endpoint, or Anthropic).
56
62
 
57
- ## Why
63
+ The import root is `agentflow`. The distribution name is `agent-workflow-sdk`.
58
64
 
59
- - Build any workflow from primitives: nodes, edges, conditional routing, and
60
- a shared state. Not a fixed loop, not a linear chain.
65
+ ## Who it's for
66
+
67
+ You drive a **local coding-agent CLI** — Claude Code, Codex, or Kiro — and you
68
+ want to orchestrate it with real control flow: branches, loops, retries, a
69
+ human approving a step, a run that survives a crash and resumes. You get that
70
+ without wiring up an API key, because the agent runs through its own CLI and
71
+ its own auth. (An API key path exists too — point a node at `OpenAIBackend` or
72
+ `AnthropicBackend` when you want one — but it's an option, not a requirement.)
73
+
74
+ It is a library, not a coding agent out of the box. If a ready-made agent CLI
75
+ already does what you need, use that. Reach for this when you need to build the
76
+ workflow *around* the agent and keep that logic independent of which backend
77
+ runs underneath.
78
+
79
+ - Build any workflow from primitives: nodes, edges, conditional routing, and a
80
+ shared state. Not a fixed loop, not a linear chain.
61
81
  - Swap the backend without touching workflow code. Agents and LLMs share one
62
82
  event vocabulary.
63
83
  - Async everywhere: the engine, backends, checkpointing, and streaming.
64
84
  - Durable by default: every super-step is checkpointed, runs resume after a
65
85
  crash, and human-in-the-loop interrupts suspend and resume a run.
66
86
 
67
-
68
87
  ## Install
69
88
 
89
+ ```bash
90
+ pip install agent-workflow-sdk
91
+ ```
92
+
70
93
  The core is dependency-free. Backends that need extra libraries are optional
71
- extras.
94
+ extras — e.g. `pip install 'agent-workflow-sdk[ollama]'` for the HTTP LLM
95
+ backends. Python 3.11+ is required.
96
+
97
+ The local-agent backends need their CLI on PATH: `claude` for Claude Code,
98
+ `codex` for Codex, `kiro-cli` for Kiro. No API key is needed — each CLI uses
99
+ its own login.
100
+
101
+ To work on the SDK itself, clone and install editable:
72
102
 
73
103
  ```bash
74
- pip install -e . # core only
75
- pip install -e '.[ollama]' # + httpx, for the Ollama LLM backend
76
- pip install -e '.[dev]' # + pytest, pytest-asyncio
104
+ git clone https://github.com/dankosorkin/agent-workflow-sdk
105
+ cd agent-workflow-sdk
106
+ pip install -e '.[dev]'
77
107
  ```
78
108
 
79
- Python 3.11+ is required. The Kiro backend needs `kiro-cli` on PATH.
109
+ ## Quickstart: a local agent, gated by a human
80
110
 
81
- ## Quickstart
82
-
83
- A graph that loops until a counter reaches a target, then stops.
111
+ A two-step workflow that puts the SDK's point on one screen. A **Claude Code**
112
+ node does the work in its own session; then the run **pauses for a human** to
113
+ approve before anything proceeds. Because every step is checkpointed, the
114
+ pause survives a process restart — you can approve now, tomorrow, or from a
115
+ different process, and the run resumes exactly where it stopped. No API key:
116
+ Claude Code runs through its own CLI login.
84
117
 
85
118
  ```python
86
119
  import asyncio
87
120
  from typing import Annotated
88
- from agentflow import Graph, START, END, State, add, append
89
-
90
- class CountState(State):
91
- n: Annotated[int, add] # updates are summed
92
- log: Annotated[list, append] # updates are concatenated
93
-
94
- async def tick(state, ctx):
95
- return {"n": 1, "log": f"tick {state.get('n', 0) + 1}"}
96
-
97
- def route(state):
98
- return "again" if state["n"] < 5 else "done"
99
-
100
- g = Graph(CountState)
101
- g.add_node("tick", tick)
102
- g.add_edge(START, "tick")
103
- g.add_conditional_edges("tick", route, {"again": "tick", "done": END})
104
- app = g.compile()
105
-
106
- print(asyncio.run(app.invoke({"n": 0, "log": []})))
121
+ from agentflow import Graph, START, END, State, FileCheckpointer, last, append
122
+ from agentflow.backends.claude_code import ClaudeCodeBackend
123
+ from agentflow.backends.base import DenyAll
124
+ from agentflow.events import TextChunk, TurnEnd
125
+
126
+ class ReviewState(State):
127
+ target: Annotated[str, last]
128
+ proposal: Annotated[str, last]
129
+ approved: Annotated[bool, last]
130
+ log: Annotated[list, append]
131
+
132
+ claude = ClaudeCodeBackend(model="sonnet", permission=DenyAll()) # read-only
133
+
134
+ async def propose(state, ctx):
135
+ text = []
136
+ async for ev in claude.prompt(
137
+ f"Review this repo and propose ONE concrete improvement to {state['target']}. "
138
+ "Describe the change; do not apply it."
139
+ ):
140
+ if isinstance(ev, TextChunk):
141
+ ctx.emit(ev) # stream tokens to the caller as they arrive
142
+ text.append(ev.text)
143
+ elif isinstance(ev, TurnEnd):
144
+ text = [ev.text] if ev.text else text
145
+ return {"proposal": "".join(text)}
146
+
147
+ async def gate(state, ctx):
148
+ # Suspend the whole run until a human answers. On a fresh run this stops
149
+ # here and checkpoints; on resume, interrupt() returns the human's value.
150
+ decision = await ctx.interrupt({"review": state["proposal"]})
151
+ return {"approved": decision == "approve", "log": f"human said: {decision}"}
152
+
153
+ g = Graph(ReviewState)
154
+ g.add_node("propose", propose)
155
+ g.add_node("gate", gate)
156
+ g.add_edge(START, "propose")
157
+ g.add_edge("propose", "gate")
158
+ g.add_edge("gate", END)
159
+
160
+ async def main():
161
+ app = g.compile(checkpointer=FileCheckpointer(".runs")) # durable
162
+ async with claude:
163
+ state = await app.invoke({"target": "the README"}, thread="demo")
164
+ # The run is now parked at the gate. Show the proposal, get a decision:
165
+ cp = await app.get_state("demo")
166
+ print(cp.interrupt_payload["review"]) # Claude's proposal
167
+ final = await app.resume("demo", value="approve")
168
+ print(final["approved"], final["log"])
169
+
170
+ asyncio.run(main())
107
171
  ```
108
172
 
109
- See `examples/hello_graph.py` and `examples/optimize_loop.py` for runnable
110
- versions.
173
+ That is the whole value in one example: a real local agent does the work, the
174
+ graph owns the control flow, a human gates the result, and durability means
175
+ the pause is not tied to this process staying alive. Swap `ClaudeCodeBackend`
176
+ for `CodexBackend`, `KiroBackend`, or an LLM backend and the workflow code
177
+ above does not change.
178
+
179
+ Prefer to learn the engine first, with no backend and nothing to install
180
+ beyond the core? `examples/hello_graph.py` is a minimal counter graph that
181
+ shows state, reducers, and edges on their own; `examples/optimize_loop.py`
182
+ shows the iterate-until-converged loop.
111
183
 
112
184
  ## Core concepts
113
185