agent-workflow-sdk 0.6.1__tar.gz → 0.6.2__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 (236) hide show
  1. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/CHANGELOG.md +10 -0
  2. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/PKG-INFO +1 -1
  3. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/__init__.py +1 -1
  4. agent_workflow_sdk-0.6.2/agentflow/backends/_winjob.py +152 -0
  5. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/kiro.py +15 -6
  6. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/pyproject.toml +1 -1
  7. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_kiro_backend.py +81 -0
  8. agent_workflow_sdk-0.6.2/tests/test_winjob.py +83 -0
  9. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.env.example +0 -0
  10. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/ci.yml +0 -0
  14. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/docs.yml +0 -0
  15. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/release.yml +0 -0
  16. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.gitignore +0 -0
  17. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/CONTRIBUTING.md +0 -0
  18. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/DESIGN.md +0 -0
  19. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/LICENSE +0 -0
  20. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/NOTICE +0 -0
  21. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/README.md +0 -0
  22. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/SECURITY.md +0 -0
  23. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/_pg.py +0 -0
  24. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/__init__.py +0 -0
  25. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/_http.py +0 -0
  26. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/anthropic.py +0 -0
  27. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/base.py +0 -0
  28. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/claude_code.py +0 -0
  29. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/cli_exec.py +0 -0
  30. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/codex.py +0 -0
  31. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/ollama.py +0 -0
  32. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/openai.py +0 -0
  33. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/__init__.py +0 -0
  34. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/_serde.py +0 -0
  35. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/base.py +0 -0
  36. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/file.py +0 -0
  37. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/memory.py +0 -0
  38. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/postgres.py +0 -0
  39. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/redis.py +0 -0
  40. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/sqlite.py +0 -0
  41. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/compiled.py +0 -0
  42. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/__init__.py +0 -0
  43. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/memory.py +0 -0
  44. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/postgres.py +0 -0
  45. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/queue.py +0 -0
  46. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/records.py +0 -0
  47. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/registry.py +0 -0
  48. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/worker.py +0 -0
  49. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/errors.py +0 -0
  50. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/events.py +0 -0
  51. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/graph.py +0 -0
  52. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/interrupts.py +0 -0
  53. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/observability.py +0 -0
  54. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/otel.py +0 -0
  55. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/__init__.py +0 -0
  56. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/budget.py +0 -0
  57. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/effects.py +0 -0
  58. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/gate.py +0 -0
  59. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/idempotent.py +0 -0
  60. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/loop.py +0 -0
  61. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/plan_executor.py +0 -0
  62. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/resilience.py +0 -0
  63. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/scoring.py +0 -0
  64. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/tool_loop.py +0 -0
  65. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/watch.py +0 -0
  66. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prometheus.py +0 -0
  67. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/py.typed +0 -0
  68. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/redaction.py +0 -0
  69. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/runtime.py +0 -0
  70. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/server/__init__.py +0 -0
  71. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/server/app.py +0 -0
  72. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/state.py +0 -0
  73. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/__init__.py +0 -0
  74. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/_util.py +0 -0
  75. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/base.py +0 -0
  76. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/memory.py +0 -0
  77. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/postgres.py +0 -0
  78. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/telemetry.py +0 -0
  79. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/eval-harness.md +0 -0
  80. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/plan-executor.md +0 -0
  81. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/token-budget.md +0 -0
  82. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/agent-backends.md +0 -0
  83. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/llm-backends.md +0 -0
  84. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/overview.md +0 -0
  85. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/permissions.md +0 -0
  86. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/retries.md +0 -0
  87. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/context.md +0 -0
  88. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/execution.md +0 -0
  89. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/nodes-and-edges.md +0 -0
  90. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/running.md +0 -0
  91. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/state.md +0 -0
  92. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/subgraphs.md +0 -0
  93. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/checkpointing.md +0 -0
  94. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/control-plane.md +0 -0
  95. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/human-in-the-loop.md +0 -0
  96. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/observability.md +0 -0
  97. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/quality-gates.md +0 -0
  98. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/store.md +0 -0
  99. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/installation.md +0 -0
  100. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/mental-model.md +0 -0
  101. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/overview.md +0 -0
  102. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/quickstart.md +0 -0
  103. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/index.md +0 -0
  104. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/patterns/long-running-loops.md +0 -0
  105. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/patterns/prebuilt.md +0 -0
  106. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/backends.md +0 -0
  107. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/budget.md +0 -0
  108. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/checkpoint.md +0 -0
  109. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/compiled.md +0 -0
  110. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/controlplane.md +0 -0
  111. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/errors.md +0 -0
  112. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/events.md +0 -0
  113. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/gate.md +0 -0
  114. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/graph.md +0 -0
  115. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/interrupts.md +0 -0
  116. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/observability.md +0 -0
  117. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/permissions.md +0 -0
  118. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/plan-executor.md +0 -0
  119. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/prebuilt.md +0 -0
  120. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/runtime.md +0 -0
  121. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/scoring.md +0 -0
  122. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/server.md +0 -0
  123. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/state.md +0 -0
  124. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/store.md +0 -0
  125. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/watch.md +0 -0
  126. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api.md +0 -0
  127. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/glossary.md +0 -0
  128. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/index.md +0 -0
  129. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/01-first-graph.md +0 -0
  130. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/02-branching.md +0 -0
  131. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/03-parallel.md +0 -0
  132. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/04-dual-state.md +0 -0
  133. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/05-backend.md +0 -0
  134. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/06-gate.md +0 -0
  135. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/07-human.md +0 -0
  136. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/08-durability.md +0 -0
  137. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/09-watch.md +0 -0
  138. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/10-plan-executor.md +0 -0
  139. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/index.md +0 -0
  140. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/qa-agent.md +0 -0
  141. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/01-first-graph.md +0 -0
  142. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/02-branching.md +0 -0
  143. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/03-parallel.md +0 -0
  144. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/04-dual-state.md +0 -0
  145. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/05-agent.md +0 -0
  146. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/06-gate.md +0 -0
  147. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/07-human.md +0 -0
  148. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/08-durability.md +0 -0
  149. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/09-watch.md +0 -0
  150. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/10-plan-executor.md +0 -0
  151. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/index.md +0 -0
  152. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/agents_demo.py +0 -0
  153. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/codebase_qa_ollama.py +0 -0
  154. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/dual_state_search.py +0 -0
  155. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/hello_graph.py +0 -0
  156. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/hitl_permission.py +0 -0
  157. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/idempotent_effect.py +0 -0
  158. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/mixed_backends.py +0 -0
  159. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/optimize_loop.py +0 -0
  160. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/plan_executor_demo.py +0 -0
  161. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/quality_gate.py +0 -0
  162. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/research_plan_implement_review.py +0 -0
  163. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/serve_control_plane.py +0 -0
  164. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/telemetry_demo.py +0 -0
  165. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tool_loop_ollama.py +0 -0
  166. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/01_first_graph.py +0 -0
  167. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/02_branching.py +0 -0
  168. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/03_parallel.py +0 -0
  169. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/04_dual_state.py +0 -0
  170. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/05_backend.py +0 -0
  171. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/06_gate.py +0 -0
  172. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/07_human.py +0 -0
  173. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/08_durability.py +0 -0
  174. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/09_watch.py +0 -0
  175. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/10_plan_executor.py +0 -0
  176. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/__init__.py +0 -0
  177. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch01_first_graph.py +0 -0
  178. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch02_branching.py +0 -0
  179. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch03_parallel.py +0 -0
  180. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch04_dual_state.py +0 -0
  181. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch05_agent.py +0 -0
  182. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch06_gate.py +0 -0
  183. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch07_human.py +0 -0
  184. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch08_durability.py +0 -0
  185. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch09_watch.py +0 -0
  186. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch10_plan_executor.py +0 -0
  187. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/db.py +0 -0
  188. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/proposer.py +0 -0
  189. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/scorer.py +0 -0
  190. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/watch_until_done.py +0 -0
  191. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/mkdocs.yml +0 -0
  192. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/conftest.py +0 -0
  193. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_anthropic_backend.py +0 -0
  194. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_backend_lifecycle.py +0 -0
  195. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_budget.py +0 -0
  196. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_checkpoint_contract.py +0 -0
  197. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_cli_backends.py +0 -0
  198. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_concurrency.py +0 -0
  199. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_controlplane.py +0 -0
  200. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_effects.py +0 -0
  201. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_gate.py +0 -0
  202. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_graph_engine.py +0 -0
  203. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_hitl_permission.py +0 -0
  204. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_http_retry.py +0 -0
  205. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_idempotent.py +0 -0
  206. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_input_validation.py +0 -0
  207. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_live_backends.py +0 -0
  208. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_observability.py +0 -0
  209. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_ollama_backend.py +0 -0
  210. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_openai_backend.py +0 -0
  211. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_otel.py +0 -0
  212. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_permission_policy.py +0 -0
  213. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_plan_executor.py +0 -0
  214. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_checkpointer.py +0 -0
  215. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_runqueue.py +0 -0
  216. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_store.py +0 -0
  217. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_loop.py +0 -0
  218. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_no_httpx.py +0 -0
  219. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prometheus.py +0 -0
  220. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_provider_resilience.py +0 -0
  221. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_redaction.py +0 -0
  222. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_redis_checkpointer.py +0 -0
  223. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_reducers.py +0 -0
  224. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_resilience.py +0 -0
  225. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_run_timeout.py +0 -0
  226. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_scoring.py +0 -0
  227. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_server.py +0 -0
  228. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_sqlite_checkpointer.py +0 -0
  229. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_state_isolation.py +0 -0
  230. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_step_atomicity.py +0 -0
  231. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_store.py +0 -0
  232. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_subgraph.py +0 -0
  233. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_sync_facade.py +0 -0
  234. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_telemetry.py +0 -0
  235. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_tool_loop.py +0 -0
  236. {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_watch.py +0 -0
@@ -4,6 +4,16 @@ 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.6.2] - 2026-11-05
8
+
9
+ ### Fixed
10
+
11
+ - On Windows the `kiro-cli` child now runs in a kill-on-close Job Object. A hard
12
+ kill ends the whole tree (`kiro-cli-chat` included), and if the host Python
13
+ process dies the OS ends it too. `taskkill /F /T` stays as the fallback. A
14
+ grandchild spawned in the first instant before the child joins the job is only
15
+ covered by that fallback.
16
+
7
17
  ## [0.6.1] - 2026-11-05
8
18
 
9
19
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-workflow-sdk
3
- Version: 0.6.1
3
+ Version: 0.6.2
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
@@ -99,7 +99,7 @@ from agentflow.state import (
99
99
  from agentflow.store import Item, MemoryStore, Store
100
100
  from agentflow.telemetry import JsonlTelemetry, MultiHooks
101
101
 
102
- __version__ = "0.6.1"
102
+ __version__ = "0.6.2"
103
103
 
104
104
  __all__ = [
105
105
  "__version__",
@@ -0,0 +1,152 @@
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
+ """Windows Job Objects: keep a child and everything it spawns on a leash.
16
+
17
+ Windows has no process groups. A Job Object created with
18
+ ``JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`` contains a process and its descendants:
19
+ terminating the job kills the whole tree, and when the last handle to the job
20
+ closes (including because *our* process crashed or was killed) the OS kills the
21
+ tree itself, so no ``kiro-cli-chat`` is left behind.
22
+
23
+ Standard library (``ctypes``) only. Every function is a safe no-op returning
24
+ ``None``/``False`` off Windows or when the API call fails, so callers can fall
25
+ back to ``taskkill``. A process is assigned after it starts, so a grandchild
26
+ spawned in that first instant is not covered; the ``taskkill /T`` fallback exists
27
+ for that.
28
+
29
+ Known limit: a process already inside a job that forbids nesting cannot be
30
+ assigned (Windows 8+ allows nested jobs, so this is rare).
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import sys
36
+ from typing import Any
37
+
38
+ _KILL_ON_JOB_CLOSE = 0x2000
39
+ _JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
40
+ _PROCESS_TERMINATE = 0x0001
41
+ _PROCESS_SET_QUOTA = 0x0100
42
+
43
+
44
+ def _kernel32() -> Any:
45
+ """kernel32 with 64-bit-safe signatures, or ``None`` when not on Windows."""
46
+ if sys.platform != "win32":
47
+ return None
48
+ import ctypes
49
+ from ctypes import wintypes
50
+
51
+ k32 = ctypes.WinDLL("kernel32", use_last_error=True)
52
+ k32.CreateJobObjectW.argtypes = [wintypes.LPVOID, wintypes.LPCWSTR]
53
+ k32.CreateJobObjectW.restype = wintypes.HANDLE
54
+ k32.SetInformationJobObject.argtypes = [
55
+ wintypes.HANDLE,
56
+ ctypes.c_int,
57
+ wintypes.LPVOID,
58
+ wintypes.DWORD,
59
+ ]
60
+ k32.SetInformationJobObject.restype = wintypes.BOOL
61
+ k32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
62
+ k32.OpenProcess.restype = wintypes.HANDLE
63
+ k32.AssignProcessToJobObject.argtypes = [wintypes.HANDLE, wintypes.HANDLE]
64
+ k32.AssignProcessToJobObject.restype = wintypes.BOOL
65
+ k32.TerminateJobObject.argtypes = [wintypes.HANDLE, wintypes.UINT]
66
+ k32.TerminateJobObject.restype = wintypes.BOOL
67
+ k32.CloseHandle.argtypes = [wintypes.HANDLE]
68
+ k32.CloseHandle.restype = wintypes.BOOL
69
+ return k32
70
+
71
+
72
+ def _limit_info() -> Any:
73
+ """A JOBOBJECT_EXTENDED_LIMIT_INFORMATION with KILL_ON_JOB_CLOSE set."""
74
+ import ctypes
75
+ from ctypes import wintypes
76
+
77
+ class BasicLimits(ctypes.Structure):
78
+ _fields_ = [
79
+ ("PerProcessUserTimeLimit", ctypes.c_int64),
80
+ ("PerJobUserTimeLimit", ctypes.c_int64),
81
+ ("LimitFlags", wintypes.DWORD),
82
+ ("MinimumWorkingSetSize", ctypes.c_size_t),
83
+ ("MaximumWorkingSetSize", ctypes.c_size_t),
84
+ ("ActiveProcessLimit", wintypes.DWORD),
85
+ ("Affinity", ctypes.c_size_t),
86
+ ("PriorityClass", wintypes.DWORD),
87
+ ("SchedulingClass", wintypes.DWORD),
88
+ ]
89
+
90
+ class IoCounters(ctypes.Structure):
91
+ _fields_ = [(n, ctypes.c_uint64) for n in ("r_ops", "w_ops", "o_ops", "r_b", "w_b", "o_b")]
92
+
93
+ class ExtendedLimits(ctypes.Structure):
94
+ _fields_ = [
95
+ ("BasicLimitInformation", BasicLimits),
96
+ ("IoInfo", IoCounters),
97
+ ("ProcessMemoryLimit", ctypes.c_size_t),
98
+ ("JobMemoryLimit", ctypes.c_size_t),
99
+ ("PeakProcessMemoryUsed", ctypes.c_size_t),
100
+ ("PeakJobMemoryUsed", ctypes.c_size_t),
101
+ ]
102
+
103
+ info = ExtendedLimits()
104
+ info.BasicLimitInformation.LimitFlags = _KILL_ON_JOB_CLOSE
105
+ return info
106
+
107
+
108
+ def attach(pid: int) -> Any | None:
109
+ """Put process ``pid`` in a new kill-on-close job and return the job handle.
110
+
111
+ Returns ``None`` (nothing to clean up) off Windows or if any step fails.
112
+ """
113
+ import ctypes
114
+
115
+ k32 = _kernel32()
116
+ if k32 is None:
117
+ return None
118
+ job = k32.CreateJobObjectW(None, None)
119
+ if not job:
120
+ return None
121
+ info = _limit_info()
122
+ ok = k32.SetInformationJobObject(
123
+ job, _JOB_OBJECT_EXTENDED_LIMIT_INFORMATION, ctypes.byref(info), ctypes.sizeof(info)
124
+ )
125
+ if not ok:
126
+ k32.CloseHandle(job)
127
+ return None
128
+ process = k32.OpenProcess(_PROCESS_SET_QUOTA | _PROCESS_TERMINATE, False, pid)
129
+ if not process:
130
+ k32.CloseHandle(job)
131
+ return None
132
+ try:
133
+ assigned = k32.AssignProcessToJobObject(job, process)
134
+ finally:
135
+ k32.CloseHandle(process)
136
+ if not assigned:
137
+ k32.CloseHandle(job)
138
+ return None
139
+ return job
140
+
141
+
142
+ def terminate(job: Any) -> bool:
143
+ """Kill every process in ``job``. ``True`` if Windows accepted it."""
144
+ k32 = _kernel32()
145
+ return bool(k32 is not None and job and k32.TerminateJobObject(job, 1))
146
+
147
+
148
+ def close(job: Any) -> None:
149
+ """Release the job handle; with kill-on-close this also kills any survivor."""
150
+ k32 = _kernel32()
151
+ if k32 is not None and job:
152
+ k32.CloseHandle(job)
@@ -38,6 +38,7 @@ from collections.abc import AsyncIterator
38
38
  from pathlib import Path
39
39
  from typing import Any, Literal
40
40
 
41
+ from agentflow.backends import _winjob
41
42
  from agentflow.backends.base import BaseAgentBackend, PermissionPolicy
42
43
  from agentflow.errors import BackendError, BackendTransportError
43
44
  from agentflow.events import (
@@ -85,6 +86,8 @@ class KiroBackend(BaseAgentBackend):
85
86
  self._close_grace = close_grace
86
87
 
87
88
  self._proc: asyncio.subprocess.Process | None = None
89
+ # Windows only: the kill-on-close Job Object holding the child's whole tree.
90
+ self._job: Any = None
88
91
  self._session_id: str | None = None
89
92
  self._request_id = 0
90
93
 
@@ -133,6 +136,8 @@ class KiroBackend(BaseAgentBackend):
133
136
  )
134
137
  except OSError as exc:
135
138
  raise BackendTransportError(f"could not start kiro-cli: {exc}") from exc
139
+ if sys.platform == "win32":
140
+ self._job = _winjob.attach(self._proc.pid)
136
141
 
137
142
  self._reader_task = asyncio.create_task(self._read_loop())
138
143
 
@@ -158,28 +163,32 @@ class KiroBackend(BaseAgentBackend):
158
163
  try:
159
164
  await asyncio.wait_for(proc.wait(), timeout=self._close_grace)
160
165
  except TimeoutError:
161
- self._stop(proc, hard=False)
166
+ self._stop(proc, hard=False, job=self._job)
162
167
  try:
163
168
  await asyncio.wait_for(proc.wait(), timeout=self._close_grace)
164
169
  except TimeoutError:
165
- self._stop(proc, hard=True)
170
+ self._stop(proc, hard=True, job=self._job)
166
171
  finally:
167
172
  if self._reader_task:
168
173
  self._reader_task.cancel()
169
174
  with contextlib.suppress(asyncio.CancelledError, Exception):
170
175
  await self._reader_task
171
176
  self._proc = None
177
+ if self._job is not None:
178
+ # Kill-on-close: also reaps any descendant that outlived a clean exit.
179
+ _winjob.close(self._job)
180
+ self._job = None
172
181
  self._fail_pending(BackendTransportError("backend closed"))
173
182
 
174
183
  @staticmethod
175
- def _stop(proc: asyncio.subprocess.Process, *, hard: bool) -> None:
184
+ def _stop(proc: asyncio.subprocess.Process, *, hard: bool, job: Any = None) -> None:
176
185
  """Stop ``proc`` by intent (soft: terminate, hard: kill). Never raises.
177
186
 
178
187
  POSIX: SIGTERM/SIGKILL to the child's process group (it leads its own
179
188
  via ``start_new_session=True``), reaching its descendants. Windows has
180
189
  no ``signal.SIGKILL``/``os.killpg``/``os.getpgid``, so nothing
181
- platform-specific is read unguarded: the hard kill runs
182
- ``taskkill /F /T`` to reap the whole tree, then ``proc.kill()``.
190
+ platform-specific is read unguarded: the hard kill terminates the Job Object
191
+ holding the whole tree (``taskkill /F /T`` if there is none), then ``proc.kill()``.
183
192
  """
184
193
  killpg = getattr(os, "killpg", None)
185
194
  getpgid = getattr(os, "getpgid", None)
@@ -188,7 +197,7 @@ class KiroBackend(BaseAgentBackend):
188
197
  with contextlib.suppress(ProcessLookupError, PermissionError, OSError, AttributeError):
189
198
  killpg(getpgid(proc.pid), sig)
190
199
  return
191
- if hard and sys.platform == "win32":
200
+ if hard and sys.platform == "win32" and not (job is not None and _winjob.terminate(job)):
192
201
  with contextlib.suppress(OSError, subprocess.SubprocessError):
193
202
  subprocess.run(
194
203
  ["taskkill", "/F", "/T", "/PID", str(proc.pid)],
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agent-workflow-sdk"
7
- version = "0.6.1"
7
+ version = "0.6.2"
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"
@@ -339,6 +339,87 @@ async def test_close_on_windows_has_no_posix_signals(fake_proc, monkeypatch):
339
339
  assert fake_proc.returncode == -9
340
340
 
341
341
 
342
+ async def _windows_close_with_job(fake_proc, monkeypatch, *, job_terminates: bool):
343
+ """close() on 'Windows' that must escalate, with a Job Object already attached."""
344
+ from agentflow.backends import _winjob
345
+
346
+ backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
347
+ setup = asyncio.create_task(_drive_setup(fake_proc))
348
+ await backend.start()
349
+ await setup
350
+ fake_proc.pid = 4242
351
+ backend._job = "JOB"
352
+
353
+ async def _never() -> int:
354
+ await asyncio.sleep(3600)
355
+ return 0
356
+
357
+ async def _fast_wait_for(coro, timeout):
358
+ coro.close()
359
+ raise TimeoutError
360
+
361
+ monkeypatch.setattr(fake_proc, "wait", _never)
362
+ monkeypatch.setattr(asyncio, "wait_for", _fast_wait_for)
363
+ monkeypatch.delattr(os, "killpg", raising=False)
364
+ monkeypatch.delattr(os, "getpgid", raising=False)
365
+ monkeypatch.setattr(sys, "platform", "win32")
366
+ events: list[str] = []
367
+ monkeypatch.setattr(
368
+ _winjob, "terminate", lambda job: events.append("job-terminate") or job_terminates
369
+ )
370
+ monkeypatch.setattr(_winjob, "close", lambda job: events.append("job-close"))
371
+ monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: events.append("taskkill"))
372
+
373
+ await backend.close()
374
+ return events, backend
375
+
376
+
377
+ async def test_windows_start_puts_the_child_in_a_job(fake_proc, monkeypatch):
378
+ from agentflow.backends import _winjob
379
+
380
+ fake_proc.pid = 777
381
+ attached: list[int] = []
382
+ monkeypatch.setattr(sys, "platform", "win32")
383
+ monkeypatch.setattr(_winjob, "attach", lambda pid: attached.append(pid) or "JOB")
384
+ backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
385
+ setup = asyncio.create_task(_drive_setup(fake_proc))
386
+ await backend.start()
387
+ await setup
388
+
389
+ assert attached == [777]
390
+ assert backend._job == "JOB"
391
+
392
+
393
+ async def test_windows_hard_kill_terminates_the_job_not_taskkill(fake_proc, monkeypatch):
394
+ events, backend = await _windows_close_with_job(fake_proc, monkeypatch, job_terminates=True)
395
+
396
+ assert events == ["job-terminate", "job-close"]
397
+ assert backend._job is None
398
+
399
+
400
+ async def test_windows_hard_kill_falls_back_to_taskkill_when_the_job_fails(fake_proc, monkeypatch):
401
+ events, _ = await _windows_close_with_job(fake_proc, monkeypatch, job_terminates=False)
402
+
403
+ assert events == ["job-terminate", "taskkill", "job-close"]
404
+
405
+
406
+ async def test_windows_clean_exit_still_releases_the_job(fake_proc, monkeypatch):
407
+ from agentflow.backends import _winjob
408
+
409
+ backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
410
+ setup = asyncio.create_task(_drive_setup(fake_proc))
411
+ await backend.start()
412
+ await setup
413
+ backend._job = "JOB"
414
+ closed: list[str] = []
415
+ monkeypatch.setattr(_winjob, "close", lambda job: closed.append(job))
416
+
417
+ await backend.close() # the fake process exits at once: no escalation
418
+
419
+ assert closed == ["JOB"]
420
+ assert backend._job is None
421
+
422
+
342
423
  async def test_turn_rpc_failure_yields_error_event(fake_proc):
343
424
  backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
344
425
  setup = asyncio.create_task(_drive_setup(fake_proc))
@@ -0,0 +1,83 @@
1
+ """Job Object wrapper, against a fake kernel32 (the real one exists only on Windows)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from agentflow.backends import _winjob
8
+
9
+
10
+ class _K32:
11
+ def __init__(self, *, fail: str | None = None) -> None:
12
+ self.fail = fail
13
+ self.calls: list[str] = []
14
+
15
+ def CreateJobObjectW(self, *a): # noqa: N802
16
+ self.calls.append("create")
17
+ return None if self.fail == "create" else 11
18
+
19
+ def SetInformationJobObject(self, job, cls, info, size): # noqa: N802
20
+ self.calls.append(f"set:{cls}:{info._obj.BasicLimitInformation.LimitFlags:#x}")
21
+ return 0 if self.fail == "set" else 1
22
+
23
+ def OpenProcess(self, access, inherit, pid): # noqa: N802
24
+ self.calls.append(f"open:{access:#x}:{pid}")
25
+ return None if self.fail == "open" else 22
26
+
27
+ def AssignProcessToJobObject(self, job, process): # noqa: N802
28
+ self.calls.append(f"assign:{job}:{process}")
29
+ return 0 if self.fail == "assign" else 1
30
+
31
+ def TerminateJobObject(self, job, code): # noqa: N802
32
+ self.calls.append(f"terminate:{job}")
33
+ return 1
34
+
35
+ def CloseHandle(self, handle): # noqa: N802
36
+ self.calls.append(f"close:{handle}")
37
+ return 1
38
+
39
+
40
+ def test_everything_is_a_noop_off_windows(monkeypatch):
41
+ monkeypatch.setattr(sys, "platform", "linux")
42
+ assert _winjob.attach(1) is None
43
+ assert _winjob.terminate(object()) is False
44
+ _winjob.close(object()) # must not raise
45
+
46
+
47
+ def test_attach_builds_a_kill_on_close_job_and_assigns_the_process(monkeypatch):
48
+ k32 = _K32()
49
+ monkeypatch.setattr(_winjob, "_kernel32", lambda: k32)
50
+
51
+ job = _winjob.attach(4242)
52
+
53
+ assert job == 11
54
+ assert k32.calls == [
55
+ "create",
56
+ "set:9:0x2000", # extended limits, KILL_ON_JOB_CLOSE
57
+ "open:0x101:4242", # SET_QUOTA | TERMINATE
58
+ "assign:11:22",
59
+ "close:22", # the process handle is released, the job handle is kept
60
+ ]
61
+
62
+
63
+ def test_attach_cleans_up_after_each_failure(monkeypatch):
64
+ for step, expected_tail in [
65
+ ("create", []),
66
+ ("set", ["close:11"]),
67
+ ("open", ["close:11"]),
68
+ ("assign", ["close:22", "close:11"]),
69
+ ]:
70
+ k32 = _K32(fail=step)
71
+ monkeypatch.setattr(_winjob, "_kernel32", lambda k=k32: k)
72
+ assert _winjob.attach(1) is None, step
73
+ assert k32.calls[len(k32.calls) - len(expected_tail) :] == expected_tail, step
74
+
75
+
76
+ def test_terminate_and_close_use_the_job_handle(monkeypatch):
77
+ k32 = _K32()
78
+ monkeypatch.setattr(_winjob, "_kernel32", lambda: k32)
79
+
80
+ assert _winjob.terminate(11) is True
81
+ _winjob.close(11)
82
+
83
+ assert k32.calls == ["terminate:11", "close:11"]