agent-workflow-sdk 0.3.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.3.0 → agent_workflow_sdk-0.4.0}/.github/workflows/docs.yml +11 -5
  2. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/CHANGELOG.md +25 -0
  3. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/DESIGN.md +17 -7
  4. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/PKG-INFO +5 -1
  5. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/__init__.py +1 -1
  6. agent_workflow_sdk-0.4.0/agentflow/server/__init__.py +46 -0
  7. agent_workflow_sdk-0.4.0/agentflow/server/app.py +205 -0
  8. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/control-plane.md +62 -1
  9. agent_workflow_sdk-0.4.0/docs/reference/api/server.md +23 -0
  10. agent_workflow_sdk-0.4.0/examples/serve_control_plane.py +89 -0
  11. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/mkdocs.yml +1 -0
  12. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/pyproject.toml +4 -1
  13. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_otel.py +4 -2
  14. agent_workflow_sdk-0.4.0/tests/test_server.py +172 -0
  15. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.env.example +0 -0
  16. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  18. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.github/workflows/ci.yml +0 -0
  20. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.github/workflows/release.yml +0 -0
  21. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/.gitignore +0 -0
  22. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/CONTRIBUTING.md +0 -0
  23. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/LICENSE +0 -0
  24. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/NOTICE +0 -0
  25. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/README.md +0 -0
  26. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/SECURITY.md +0 -0
  27. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/_pg.py +0 -0
  28. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/__init__.py +0 -0
  29. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/_http.py +0 -0
  30. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/anthropic.py +0 -0
  31. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/base.py +0 -0
  32. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/claude_code.py +0 -0
  33. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/cli_exec.py +0 -0
  34. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/codex.py +0 -0
  35. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/kiro.py +0 -0
  36. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/ollama.py +0 -0
  37. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/backends/openai.py +0 -0
  38. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/__init__.py +0 -0
  39. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/_serde.py +0 -0
  40. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/base.py +0 -0
  41. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/file.py +0 -0
  42. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/memory.py +0 -0
  43. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/postgres.py +0 -0
  44. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/redis.py +0 -0
  45. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/checkpoint/sqlite.py +0 -0
  46. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/compiled.py +0 -0
  47. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/__init__.py +0 -0
  48. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/memory.py +0 -0
  49. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/postgres.py +0 -0
  50. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/queue.py +0 -0
  51. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/records.py +0 -0
  52. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/registry.py +0 -0
  53. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/controlplane/worker.py +0 -0
  54. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/errors.py +0 -0
  55. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/events.py +0 -0
  56. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/graph.py +0 -0
  57. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/interrupts.py +0 -0
  58. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/observability.py +0 -0
  59. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/otel.py +0 -0
  60. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/__init__.py +0 -0
  61. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/effects.py +0 -0
  62. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/gate.py +0 -0
  63. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/idempotent.py +0 -0
  64. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/loop.py +0 -0
  65. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/resilience.py +0 -0
  66. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/tool_loop.py +0 -0
  67. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prebuilt/watch.py +0 -0
  68. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/prometheus.py +0 -0
  69. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/py.typed +0 -0
  70. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/redaction.py +0 -0
  71. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/runtime.py +0 -0
  72. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/state.py +0 -0
  73. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/store/__init__.py +0 -0
  74. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/store/_util.py +0 -0
  75. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/store/base.py +0 -0
  76. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/store/memory.py +0 -0
  77. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/store/postgres.py +0 -0
  78. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/agentflow/telemetry.py +0 -0
  79. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/backends/agent-backends.md +0 -0
  80. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/backends/llm-backends.md +0 -0
  81. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/backends/overview.md +0 -0
  82. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/backends/permissions.md +0 -0
  83. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/backends/retries.md +0 -0
  84. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/context.md +0 -0
  85. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/execution.md +0 -0
  86. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/nodes-and-edges.md +0 -0
  87. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/running.md +0 -0
  88. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/state.md +0 -0
  89. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/concepts/subgraphs.md +0 -0
  90. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/checkpointing.md +0 -0
  91. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/human-in-the-loop.md +0 -0
  92. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/observability.md +0 -0
  93. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/quality-gates.md +0 -0
  94. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/durability/store.md +0 -0
  95. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/get-started/installation.md +0 -0
  96. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/get-started/mental-model.md +0 -0
  97. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/get-started/overview.md +0 -0
  98. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/get-started/quickstart.md +0 -0
  99. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/index.md +0 -0
  100. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/patterns/long-running-loops.md +0 -0
  101. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/patterns/prebuilt.md +0 -0
  102. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/patterns/tutorial-qa-agent.md +0 -0
  103. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/backends.md +0 -0
  104. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/checkpoint.md +0 -0
  105. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/compiled.md +0 -0
  106. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/controlplane.md +0 -0
  107. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/errors.md +0 -0
  108. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/events.md +0 -0
  109. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/gate.md +0 -0
  110. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/graph.md +0 -0
  111. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/interrupts.md +0 -0
  112. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/observability.md +0 -0
  113. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/permissions.md +0 -0
  114. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/prebuilt.md +0 -0
  115. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/runtime.md +0 -0
  116. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/state.md +0 -0
  117. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/store.md +0 -0
  118. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api/watch.md +0 -0
  119. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/api.md +0 -0
  120. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/docs/reference/glossary.md +0 -0
  121. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/agents_demo.py +0 -0
  122. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/codebase_qa_ollama.py +0 -0
  123. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/hello_graph.py +0 -0
  124. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/hitl_permission.py +0 -0
  125. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/idempotent_effect.py +0 -0
  126. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/mixed_backends.py +0 -0
  127. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/optimize_loop.py +0 -0
  128. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/quality_gate.py +0 -0
  129. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/research_plan_implement_review.py +0 -0
  130. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/telemetry_demo.py +0 -0
  131. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/tool_loop_ollama.py +0 -0
  132. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/examples/watch_until_done.py +0 -0
  133. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/conftest.py +0 -0
  134. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_anthropic_backend.py +0 -0
  135. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_backend_lifecycle.py +0 -0
  136. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_checkpoint_contract.py +0 -0
  137. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_cli_backends.py +0 -0
  138. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_concurrency.py +0 -0
  139. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_controlplane.py +0 -0
  140. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_effects.py +0 -0
  141. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_gate.py +0 -0
  142. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_graph_engine.py +0 -0
  143. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_hitl_permission.py +0 -0
  144. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_http_retry.py +0 -0
  145. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_idempotent.py +0 -0
  146. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_input_validation.py +0 -0
  147. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_kiro_backend.py +0 -0
  148. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_live_backends.py +0 -0
  149. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_observability.py +0 -0
  150. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_ollama_backend.py +0 -0
  151. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_openai_backend.py +0 -0
  152. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_permission_policy.py +0 -0
  153. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_postgres_checkpointer.py +0 -0
  154. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_postgres_runqueue.py +0 -0
  155. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_postgres_store.py +0 -0
  156. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_prebuilt_loop.py +0 -0
  157. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_prebuilt_no_httpx.py +0 -0
  158. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_prometheus.py +0 -0
  159. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_provider_resilience.py +0 -0
  160. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_redaction.py +0 -0
  161. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_redis_checkpointer.py +0 -0
  162. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_resilience.py +0 -0
  163. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_run_timeout.py +0 -0
  164. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_sqlite_checkpointer.py +0 -0
  165. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_state_isolation.py +0 -0
  166. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_step_atomicity.py +0 -0
  167. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_store.py +0 -0
  168. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_subgraph.py +0 -0
  169. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_sync_facade.py +0 -0
  170. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_telemetry.py +0 -0
  171. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_tool_loop.py +0 -0
  172. {agent_workflow_sdk-0.3.0 → agent_workflow_sdk-0.4.0}/tests/test_watch.py +0 -0
@@ -1,12 +1,18 @@
1
1
  name: Docs
2
2
 
3
- # Build the mkdocs site and deploy it to GitHub Pages on a version tag, or on
4
- # demand from the Actions tab. Enable Pages once (Settings -> Pages -> Source:
5
- # GitHub Actions) for the deploy step to publish.
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.
6
8
  on:
7
9
  push:
8
- tags:
9
- - "v*"
10
+ branches: [main]
11
+ paths:
12
+ - "docs/**"
13
+ - "mkdocs.yml"
14
+ - "agentflow/**"
15
+ - ".github/workflows/docs.yml"
10
16
  workflow_dispatch:
11
17
 
12
18
  # Allow one concurrent deploy; a newer run supersedes an in-flight one.
@@ -4,6 +4,31 @@ 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
+
7
32
  ## [0.3.0] - 2026-10-02
8
33
 
9
34
  ### Added
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-workflow-sdk
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
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
@@ -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,6 +43,8 @@ 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
@@ -97,7 +97,7 @@ from agentflow.state import (
97
97
  from agentflow.store import Item, MemoryStore, Store
98
98
  from agentflow.telemetry import JsonlTelemetry, MultiHooks
99
99
 
100
- __version__ = "0.3.0"
100
+ __version__ = "0.4.0"
101
101
 
102
102
  __all__ = [
103
103
  "__version__",
@@ -0,0 +1,46 @@
1
+ # Copyright 2026 Daniel Sorkin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """An HTTP adapter over the control-plane run queue (optional ``server`` extra).
16
+
17
+ This is the thin service layer DESIGN.md defers to: a FastAPI app that maps 1:1
18
+ onto :class:`~agentflow.controlplane.queue.RunQueue` methods, so a run can be
19
+ enqueued, inspected, resumed, or cancelled over HTTP from anywhere. It adds no
20
+ behavior the queue does not already have.
21
+
22
+ from agentflow import PostgresRunQueue
23
+ from agentflow.server import create_app
24
+
25
+ app = create_app(PostgresRunQueue(dsn)) # an ASGI app; serve with uvicorn
26
+
27
+ What it exposes is the *enqueue and observe* plane: ``POST /runs``, ``GET
28
+ /runs/{id}``, ``GET /runs``, ``POST /runs/{id}/resume``, ``POST
29
+ /runs/{id}/cancel``, ``GET /stats``, ``GET /healthz``. The worker protocol
30
+ (``claim`` / ``heartbeat`` / ``complete``) is deliberately *not* exposed —
31
+ those mutate run state and belong to workers, which talk to the queue directly
32
+ (in-process or via the same Postgres queue), not over this API.
33
+
34
+ Scope, stated plainly: there is no authentication, authorization, rate
35
+ limiting, or multi-tenancy here — DESIGN.md defers those as properties of a
36
+ service. Run this inside your own trust boundary, or behind a reverse proxy
37
+ that supplies them. It is an adapter, not a hardened public endpoint.
38
+
39
+ Requires the ``server`` extra: ``pip install 'agent-workflow-sdk[server]'``.
40
+ Serve the returned ASGI app with any ASGI server (e.g. ``uvicorn``), which you
41
+ install separately.
42
+ """
43
+
44
+ from agentflow.server.app import create_app
45
+
46
+ __all__ = ["create_app"]
@@ -0,0 +1,205 @@
1
+ # Copyright 2026 Daniel Sorkin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """FastAPI app factory mapping HTTP routes onto a ``RunQueue``.
16
+
17
+ Each route is a direct adapter over one queue method; see the package docstring
18
+ for the exposed surface and the explicit non-goals (no auth, no worker
19
+ protocol). Response/request shapes are Pydantic models so the generated OpenAPI
20
+ docs (``/docs``) are accurate.
21
+
22
+ Note: this module intentionally does NOT use ``from __future__ import
23
+ annotations``. FastAPI resolves endpoint annotations with ``get_type_hints``,
24
+ which cannot re-evaluate a stringized ``Depends(get_queue)`` that references a
25
+ function-local dependency — real annotation objects are required.
26
+ """
27
+
28
+ from collections.abc import Callable
29
+ from typing import Annotated, Any
30
+
31
+ try:
32
+ from fastapi import Depends, FastAPI, HTTPException, Query
33
+ from pydantic import BaseModel, Field
34
+ except ModuleNotFoundError as exc: # pragma: no cover - import-time guard
35
+ raise ModuleNotFoundError(
36
+ "agentflow.server requires FastAPI. Install the extra: "
37
+ "pip install 'agent-workflow-sdk[server]'"
38
+ ) from exc
39
+
40
+ from agentflow.controlplane.queue import RunQueue
41
+ from agentflow.controlplane.records import QueueStats, RunRecord
42
+ from agentflow.errors import RunNotFound
43
+
44
+ __all__ = ["create_app"]
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Wire models (what crosses the HTTP boundary)
49
+ # ---------------------------------------------------------------------------
50
+
51
+
52
+ class EnqueueRequest(BaseModel):
53
+ """Body for ``POST /runs`` — ask to run a registered graph."""
54
+
55
+ graph: str = Field(..., description="Name of a graph registered on the worker side.")
56
+ input: dict[str, Any] = Field(default_factory=dict, description="Initial state for the run.")
57
+ thread: str | None = Field(None, description="Checkpoint thread; defaults to the run id.")
58
+
59
+
60
+ class ResumeRequest(BaseModel):
61
+ """Body for ``POST /runs/{run_id}/resume`` — answer an interrupted run."""
62
+
63
+ value: Any = Field(None, description="Value fed to the interrupted node on resume.")
64
+
65
+
66
+ class RunResponse(BaseModel):
67
+ """One run's queue record."""
68
+
69
+ run_id: str
70
+ graph: str
71
+ thread: str
72
+ status: str
73
+ input: dict[str, Any] = Field(default_factory=dict)
74
+ resume_value: Any = None
75
+ error: str | None = None
76
+ attempt: int = 0
77
+ created_at: str = ""
78
+ updated_at: str = ""
79
+ lease_until: str | None = None
80
+ wake_at: str | None = None
81
+ cancel_requested: bool = False
82
+
83
+ @classmethod
84
+ def of(cls, rec: RunRecord) -> "RunResponse":
85
+ return cls(
86
+ run_id=rec.run_id,
87
+ graph=rec.graph,
88
+ thread=rec.thread,
89
+ status=rec.status,
90
+ input=dict(rec.input),
91
+ resume_value=rec.resume_value,
92
+ error=rec.error,
93
+ attempt=rec.attempt,
94
+ created_at=rec.created_at,
95
+ updated_at=rec.updated_at,
96
+ lease_until=rec.lease_until,
97
+ wake_at=rec.wake_at,
98
+ cancel_requested=rec.cancel_requested,
99
+ )
100
+
101
+
102
+ class CancelResponse(BaseModel):
103
+ cancelled: bool = Field(
104
+ ..., description="True if the run existed and was not already terminal."
105
+ )
106
+
107
+
108
+ class StatsResponse(BaseModel):
109
+ total: int = 0
110
+ by_status: dict[str, int] = Field(default_factory=dict)
111
+ expired_leases: int = 0
112
+
113
+ @classmethod
114
+ def of(cls, s: QueueStats) -> "StatsResponse":
115
+ return cls(total=s.total, by_status=dict(s.by_status), expired_leases=s.expired_leases)
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # App factory
120
+ # ---------------------------------------------------------------------------
121
+
122
+
123
+ def create_app(
124
+ queue: RunQueue,
125
+ *,
126
+ pool_health: Callable[[], Any] | None = None,
127
+ title: str = "AgentFlow control plane",
128
+ ) -> FastAPI:
129
+ """Build a FastAPI app over ``queue``.
130
+
131
+ ``queue`` is any :class:`~agentflow.controlplane.queue.RunQueue` (Memory for
132
+ a single process, Postgres for a shared/durable one). ``pool_health``, if
133
+ given, is a zero-arg callable returning a
134
+ :class:`~agentflow.controlplane.records.PoolHealth` (typically
135
+ ``pool.health``); ``GET /healthz`` reports it when present. The app adds no
136
+ behavior of its own — every route forwards to a queue method.
137
+ """
138
+ app = FastAPI(title=title, version="1")
139
+
140
+ def get_queue() -> RunQueue:
141
+ return queue
142
+
143
+ # FastAPI dependency injected via the Annotated form (the modern idiom:
144
+ # keeps Depends() out of the parameter default, so no ruff B008). Inlined in
145
+ # each signature rather than aliased to a local var, because FastAPI
146
+ # resolves annotations by name and would not see a function-local alias.
147
+ @app.post("/runs", response_model=RunResponse, status_code=201, tags=["runs"])
148
+ async def enqueue_run(
149
+ body: EnqueueRequest, q: Annotated[RunQueue, Depends(get_queue)]
150
+ ) -> RunResponse:
151
+ rec = await q.enqueue(body.graph, body.input, thread=body.thread)
152
+ return RunResponse.of(rec)
153
+
154
+ @app.get("/runs/{run_id}", response_model=RunResponse, tags=["runs"])
155
+ async def get_run(run_id: str, q: Annotated[RunQueue, Depends(get_queue)]) -> RunResponse:
156
+ rec = await q.get(run_id)
157
+ if rec is None:
158
+ raise HTTPException(status_code=404, detail=f"no run with id {run_id!r}")
159
+ return RunResponse.of(rec)
160
+
161
+ @app.get("/runs", response_model=list[RunResponse], tags=["runs"])
162
+ async def list_runs(
163
+ q: Annotated[RunQueue, Depends(get_queue)],
164
+ status: Annotated[str | None, Query()] = None,
165
+ graph: Annotated[str | None, Query()] = None,
166
+ limit: Annotated[int, Query(ge=1, le=1000)] = 100,
167
+ offset: Annotated[int, Query(ge=0)] = 0,
168
+ ) -> list[RunResponse]:
169
+ recs = await q.list(status=status, graph=graph, limit=limit, offset=offset)
170
+ return [RunResponse.of(r) for r in recs]
171
+
172
+ @app.post("/runs/{run_id}/resume", response_model=RunResponse, tags=["runs"])
173
+ async def resume_run(
174
+ run_id: str, body: ResumeRequest, q: Annotated[RunQueue, Depends(get_queue)]
175
+ ) -> RunResponse:
176
+ try:
177
+ rec = await q.enqueue_resume(run_id, body.value)
178
+ except RunNotFound:
179
+ raise HTTPException(status_code=404, detail=f"no run with id {run_id!r}") from None
180
+ return RunResponse.of(rec)
181
+
182
+ @app.post("/runs/{run_id}/cancel", response_model=CancelResponse, tags=["runs"])
183
+ async def cancel_run(run_id: str, q: Annotated[RunQueue, Depends(get_queue)]) -> CancelResponse:
184
+ cancelled = await q.request_cancel(run_id)
185
+ return CancelResponse(cancelled=cancelled)
186
+
187
+ @app.get("/stats", response_model=StatsResponse, tags=["monitoring"])
188
+ async def get_stats(q: Annotated[RunQueue, Depends(get_queue)]) -> StatsResponse:
189
+ return StatsResponse.of(await q.stats())
190
+
191
+ @app.get("/healthz", tags=["monitoring"])
192
+ async def healthz() -> dict[str, Any]:
193
+ body: dict[str, Any] = {"status": "ok"}
194
+ if pool_health is not None:
195
+ h = pool_health()
196
+ body["pool"] = {
197
+ "workers": h.workers,
198
+ "alive": h.alive,
199
+ "busy": h.busy,
200
+ "idle": h.idle,
201
+ "healthy": h.healthy,
202
+ }
203
+ return body
204
+
205
+ return app
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## What it is, and is not
6
6
 
7
- The control plane is a library layer, not a service. There is no bundled HTTP server, auth, or tenancy — those are properties of a service you build on top, and are deliberately out of scope. What you get is the durable machinery: a queue, a registry, and workers, all as plain objects you drive.
7
+ The control plane is a library layer first. What you get is the durable machinery: a queue, a registry, and workers, all as plain objects you drive. An optional HTTP layer (the `server` extra) wraps the queue so you can drive it over the network — covered at the end of this chapter. There is still no auth or tenancy anywhere in it; those are properties of a service you put in front, and stay out of scope.
8
8
 
9
9
  ## The pieces
10
10
 
@@ -105,6 +105,67 @@ if not health.healthy:
105
105
 
106
106
  A rising `expired_leases` is the signal that workers are dying or falling behind their heartbeat. `health.healthy` is False when any configured worker loop is not alive — a readiness signal you can expose directly.
107
107
 
108
+ ## Over HTTP
109
+
110
+ Everything so far drives the queue from Python. The optional `server` extra wraps the same queue in a FastAPI app, so you can enqueue and observe runs over the network — from a different language, a web UI, or a `curl` in a shell.
111
+
112
+ ```bash
113
+ pip install 'agent-workflow-sdk[server]'
114
+ ```
115
+
116
+ `create_app` takes a `RunQueue` and returns an ASGI app. It adds no behavior of its own; each route forwards to one queue method.
117
+
118
+ ```python
119
+ from agentflow import PostgresRunQueue
120
+ from agentflow.server import create_app
121
+
122
+ queue = PostgresRunQueue(dsn)
123
+ app = create_app(queue) # an ASGI app
124
+ ```
125
+
126
+ Serve it with any ASGI server. `uvicorn` is a common choice and you install it yourself:
127
+
128
+ ```bash
129
+ uvicorn myapp:app --port 8000
130
+ ```
131
+
132
+ The routes map 1:1 onto the queue methods from earlier in this chapter:
133
+
134
+ - `POST /runs` enqueues a run. Body: `{"graph": "...", "input": {...}, "thread": "..."}` (thread optional). Returns `201` with the run record.
135
+ - `GET /runs/{id}` returns one run record, or `404` if there is no such run.
136
+ - `GET /runs` lists runs. Query params: `status`, `graph`, `limit` (1–1000), `offset`.
137
+ - `POST /runs/{id}/resume` re-queues an interrupted run. Body: `{"value": ...}`. `404` if there is no such run.
138
+ - `POST /runs/{id}/cancel` requests cancellation. Returns `{"cancelled": true}` unless the run was unknown or already terminal.
139
+ - `GET /stats` returns the `QueueStats` snapshot (`total`, `by_status`, `expired_leases`).
140
+ - `GET /healthz` returns `{"status": "ok"}`, plus a `pool` block when you pass `pool_health`.
141
+
142
+ Because the models are typed, FastAPI serves interactive OpenAPI docs at `/docs` and the raw schema at `/openapi.json` with no extra work.
143
+
144
+ A quick enqueue over HTTP:
145
+
146
+ ```bash
147
+ curl -X POST localhost:8000/runs \
148
+ -H 'content-type: application/json' \
149
+ -d '{"graph": "summarize", "input": {"text": "..."}}'
150
+ ```
151
+
152
+ Wire the pool's health into `/healthz` by passing the callable:
153
+
154
+ ```python
155
+ pool = WorkerPool(queue, registry, concurrency=4)
156
+ app = create_app(queue, pool_health=lambda: pool.health())
157
+ ```
158
+
159
+ ### What it does not expose
160
+
161
+ The worker protocol — `claim`, `heartbeat`, `complete` — is deliberately absent from the HTTP surface. Those methods mutate run state and belong to workers, which talk to the queue directly (in-process, or via the same `PostgresRunQueue`), not over this API. The HTTP layer is the enqueue-and-observe plane only.
162
+
163
+ ### Trust boundary
164
+
165
+ There is no authentication, authorization, rate limiting, or multi-tenancy in this app. It is an adapter over the queue, not a hardened public endpoint. Run it inside your own trust boundary, or behind a reverse proxy or gateway that supplies those. Anyone who can reach it can enqueue, cancel, and resume any run.
166
+
167
+ See the [HTTP server API reference](../reference/api/server.md) for the request and response models.
168
+
108
169
  ## When to use the control plane
109
170
 
110
171
  - Use inline `invoke`/`stream` when the run belongs to the calling process — a request handler, a script, a test.
@@ -0,0 +1,23 @@
1
+ # HTTP server
2
+
3
+ The optional FastAPI adapter over a run queue. See the [Control plane over HTTP](../../durability/control-plane.md#over-http) guide.
4
+
5
+ Requires the `server` extra: `pip install 'agent-workflow-sdk[server]'`.
6
+
7
+ ::: agentflow.server
8
+ options:
9
+ members:
10
+ - create_app
11
+
12
+ ## Wire models
13
+
14
+ The request and response bodies that cross the HTTP boundary. These are Pydantic models, so they also drive the generated OpenAPI schema served at `/openapi.json` and the interactive docs at `/docs`.
15
+
16
+ ::: agentflow.server.app
17
+ options:
18
+ members:
19
+ - EnqueueRequest
20
+ - ResumeRequest
21
+ - RunResponse
22
+ - CancelResponse
23
+ - StatsResponse
@@ -0,0 +1,89 @@
1
+ # Copyright 2026 Daniel Sorkin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Drive the control plane over HTTP.
16
+
17
+ Run: python examples/serve_control_plane.py
18
+ Needs the server extra: pip install 'agent-workflow-sdk[server]'
19
+
20
+ ``create_app(queue)`` returns a FastAPI ASGI app that maps 1:1 onto the run
21
+ queue: POST /runs enqueues, GET /runs[/{id}] observes, resume/cancel manage,
22
+ /stats and /healthz monitor. The app adds no behavior of its own — every route
23
+ forwards to one queue method.
24
+
25
+ To keep this offline and self-contained, it exercises the app through FastAPI's
26
+ TestClient (no socket, no running server). In a real deployment you would skip
27
+ the client and serve the module-level ``app`` with an ASGI server instead:
28
+
29
+ uvicorn examples.serve_control_plane:app --port 8000
30
+
31
+ and a worker (usually a separate process) would drain the same queue. Here the
32
+ queue is a MemoryRunQueue, so there is nothing to actually execute the run — we
33
+ only demonstrate the HTTP surface and the run lifecycle up to "queued".
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from agentflow import MemoryRunQueue, PoolHealth
39
+ from agentflow.server import create_app
40
+
41
+ # A module-level ASGI app, so `uvicorn examples.serve_control_plane:app` works.
42
+ # pool_health is optional; here it is a static stand-in for a WorkerPool's
43
+ # health, which /healthz surfaces as a readiness signal.
44
+ queue = MemoryRunQueue()
45
+ app = create_app(queue, pool_health=lambda: PoolHealth(workers=4, alive=4, busy=1))
46
+
47
+
48
+ def main() -> None:
49
+ try:
50
+ from fastapi.testclient import TestClient
51
+ except ModuleNotFoundError:
52
+ raise SystemExit(
53
+ "this example needs the server extra: pip install 'agent-workflow-sdk[server]'"
54
+ ) from None
55
+
56
+ client = TestClient(app)
57
+
58
+ print("POST /runs")
59
+ created = client.post("/runs", json={"graph": "summarize", "input": {"text": "..."}})
60
+ run = created.json()
61
+ print(f" -> {created.status_code} {run['status']} id={run['run_id']}")
62
+
63
+ print("GET /runs/{id}")
64
+ got = client.get(f"/runs/{run['run_id']}").json()
65
+ print(f" -> status={got['status']} graph={got['graph']}")
66
+
67
+ print("GET /runs?graph=summarize")
68
+ listed = client.get("/runs", params={"graph": "summarize"}).json()
69
+ print(f" -> {len(listed)} run(s)")
70
+
71
+ print("GET /stats")
72
+ stats = client.get("/stats").json()
73
+ print(f" -> total={stats['total']} by_status={stats['by_status']}")
74
+
75
+ print("GET /healthz")
76
+ print(f" -> {client.get('/healthz').json()}")
77
+
78
+ print("POST /runs/{id}/cancel")
79
+ rid = run["run_id"]
80
+ cancelled = client.post(f"/runs/{rid}/cancel").json()
81
+ print(f" -> cancelled={cancelled['cancelled']}")
82
+ now = client.get(f"/runs/{rid}").json()["status"]
83
+ print(f" -> status now: {now}")
84
+
85
+ print("\ninteractive OpenAPI docs would be at /docs; schema at /openapi.json")
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
@@ -144,6 +144,7 @@ nav:
144
144
  - Checkpointing: reference/api/checkpoint.md
145
145
  - Store: reference/api/store.md
146
146
  - Control plane: reference/api/controlplane.md
147
+ - HTTP server: reference/api/server.md
147
148
  - Observability: reference/api/observability.md
148
149
  - Prebuilt patterns: reference/api/prebuilt.md
149
150
  - Quality gates: reference/api/gate.md
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agent-workflow-sdk"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Async-first SDK for orchestrating local coding-agent CLIs (Claude Code, Codex, Kiro) and LLMs as durable, graph-based workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -31,6 +31,7 @@ otel = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"]
31
31
  redis = ["redis>=5"]
32
32
  postgres = ["asyncpg>=0.29"]
33
33
  prometheus = ["prometheus-client>=0.20"]
34
+ server = ["fastapi>=0.110"]
34
35
  docs = ["mkdocs-material>=9.5", "mkdocstrings[python]>=0.26"]
35
36
  dev = [
36
37
  "pytest>=8",
@@ -41,6 +42,8 @@ dev = [
41
42
  "pip-audit>=2.7",
42
43
  "fakeredis>=2.20",
43
44
  "prometheus-client>=0.20",
45
+ "fastapi>=0.110",
46
+ "httpx>=0.27",
44
47
  ]
45
48
 
46
49
  [project.urls]
@@ -21,8 +21,10 @@ from typing import Annotated
21
21
  import pytest
22
22
  from agentflow import END, START, Graph, State, add
23
23
 
24
- # Skip the whole module if the otel extra isn't installed.
25
- otel = pytest.importorskip("opentelemetry")
24
+ # Skip the whole module if the otel extra isn't installed. Guard on
25
+ # opentelemetry.sdk, not the bare opentelemetry namespace: the API package can
26
+ # be present transitively while the SDK (the otel extra) is not.
27
+ otel = pytest.importorskip("opentelemetry.sdk")
26
28
 
27
29
  from agentflow.otel import OtelHooks # noqa: E402
28
30
  from opentelemetry.sdk.trace import TracerProvider # noqa: E402