agent-workflow-sdk 0.6.0__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.
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/CHANGELOG.md +20 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/PKG-INFO +1 -1
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/__init__.py +1 -1
- agent_workflow_sdk-0.6.2/agentflow/backends/_winjob.py +152 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/kiro.py +38 -15
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/pyproject.toml +1 -1
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_kiro_backend.py +117 -0
- agent_workflow_sdk-0.6.2/tests/test_winjob.py +83 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.env.example +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/workflows/ci.yml +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/workflows/docs.yml +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/workflows/release.yml +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.gitignore +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/CONTRIBUTING.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/DESIGN.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/LICENSE +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/NOTICE +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/README.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/SECURITY.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/_pg.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/_http.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/anthropic.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/base.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/claude_code.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/cli_exec.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/codex.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/ollama.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/backends/openai.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/_serde.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/base.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/file.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/memory.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/postgres.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/redis.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/sqlite.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/compiled.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/memory.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/postgres.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/queue.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/records.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/registry.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/worker.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/errors.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/events.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/graph.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/interrupts.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/observability.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/otel.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/budget.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/effects.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/gate.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/idempotent.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/loop.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/resilience.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/scoring.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/tool_loop.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/watch.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/prometheus.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/py.typed +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/redaction.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/runtime.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/server/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/server/app.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/state.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/store/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/store/_util.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/store/base.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/store/memory.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/store/postgres.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/agentflow/telemetry.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/design-notes/eval-harness.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/design-notes/plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/design-notes/token-budget.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/backends/agent-backends.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/backends/llm-backends.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/backends/overview.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/backends/permissions.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/backends/retries.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/context.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/execution.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/nodes-and-edges.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/running.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/state.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/concepts/subgraphs.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/checkpointing.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/control-plane.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/human-in-the-loop.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/observability.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/quality-gates.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/durability/store.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/get-started/installation.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/get-started/mental-model.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/get-started/overview.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/get-started/quickstart.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/index.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/patterns/long-running-loops.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/patterns/prebuilt.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/backends.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/budget.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/checkpoint.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/compiled.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/controlplane.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/errors.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/events.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/gate.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/graph.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/interrupts.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/observability.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/permissions.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/prebuilt.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/runtime.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/scoring.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/server.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/state.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/store.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api/watch.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/api.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/reference/glossary.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/index.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/01-first-graph.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/02-branching.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/03-parallel.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/04-dual-state.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/05-backend.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/06-gate.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/07-human.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/08-durability.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/09-watch.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/10-plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/index.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/qa-agent.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/01-first-graph.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/02-branching.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/03-parallel.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/04-dual-state.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/05-agent.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/06-gate.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/07-human.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/08-durability.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/09-watch.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/10-plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/index.md +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/agents_demo.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/codebase_qa_ollama.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/dual_state_search.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/hello_graph.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/hitl_permission.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/idempotent_effect.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/mixed_backends.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/optimize_loop.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/plan_executor_demo.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/quality_gate.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/research_plan_implement_review.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/serve_control_plane.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/telemetry_demo.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tool_loop_ollama.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/01_first_graph.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/02_branching.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/03_parallel.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/04_dual_state.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/05_backend.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/06_gate.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/07_human.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/08_durability.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/09_watch.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/10_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/__init__.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch01_first_graph.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch02_branching.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch03_parallel.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch04_dual_state.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch05_agent.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch06_gate.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch07_human.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch08_durability.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch09_watch.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch10_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/db.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/proposer.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/scorer.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/examples/watch_until_done.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/mkdocs.yml +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/conftest.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_anthropic_backend.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_backend_lifecycle.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_budget.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_checkpoint_contract.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_cli_backends.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_concurrency.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_controlplane.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_effects.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_gate.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_graph_engine.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_hitl_permission.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_http_retry.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_idempotent.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_input_validation.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_live_backends.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_observability.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_ollama_backend.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_openai_backend.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_otel.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_permission_policy.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_postgres_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_postgres_runqueue.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_postgres_store.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_loop.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_no_httpx.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_prometheus.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_provider_resilience.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_redaction.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_redis_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_reducers.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_resilience.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_run_timeout.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_scoring.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_server.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_sqlite_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_state_isolation.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_step_atomicity.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_store.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_subgraph.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_sync_facade.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_telemetry.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_tool_loop.py +0 -0
- {agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/tests/test_watch.py +0 -0
|
@@ -4,6 +4,26 @@ 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
|
+
|
|
17
|
+
## [0.6.1] - 2026-11-05
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `KiroBackend.close()` no longer raises `AttributeError` on Windows, where
|
|
22
|
+
`signal.SIGKILL`, `os.killpg` and `os.getpgid` do not exist. Shutdown escalates
|
|
23
|
+
by intent (terminate, then kill): POSIX still signals the process group; on
|
|
24
|
+
Windows the hard kill runs `taskkill /F /T` so grandchildren such as
|
|
25
|
+
`kiro-cli-chat` are reaped, then `proc.kill()`.
|
|
26
|
+
|
|
7
27
|
## [0.6.0] - 2026-09-29
|
|
8
28
|
|
|
9
29
|
### Changed (breaking)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-workflow-sdk
|
|
3
|
-
Version: 0.6.
|
|
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
|
|
@@ -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)
|
|
@@ -32,10 +32,13 @@ import contextlib
|
|
|
32
32
|
import json
|
|
33
33
|
import os
|
|
34
34
|
import signal
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
35
37
|
from collections.abc import AsyncIterator
|
|
36
38
|
from pathlib import Path
|
|
37
39
|
from typing import Any, Literal
|
|
38
40
|
|
|
41
|
+
from agentflow.backends import _winjob
|
|
39
42
|
from agentflow.backends.base import BaseAgentBackend, PermissionPolicy
|
|
40
43
|
from agentflow.errors import BackendError, BackendTransportError
|
|
41
44
|
from agentflow.events import (
|
|
@@ -83,6 +86,8 @@ class KiroBackend(BaseAgentBackend):
|
|
|
83
86
|
self._close_grace = close_grace
|
|
84
87
|
|
|
85
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
|
|
86
91
|
self._session_id: str | None = None
|
|
87
92
|
self._request_id = 0
|
|
88
93
|
|
|
@@ -131,6 +136,8 @@ class KiroBackend(BaseAgentBackend):
|
|
|
131
136
|
)
|
|
132
137
|
except OSError as exc:
|
|
133
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)
|
|
134
141
|
|
|
135
142
|
self._reader_task = asyncio.create_task(self._read_loop())
|
|
136
143
|
|
|
@@ -156,35 +163,51 @@ class KiroBackend(BaseAgentBackend):
|
|
|
156
163
|
try:
|
|
157
164
|
await asyncio.wait_for(proc.wait(), timeout=self._close_grace)
|
|
158
165
|
except TimeoutError:
|
|
159
|
-
self.
|
|
166
|
+
self._stop(proc, hard=False, job=self._job)
|
|
160
167
|
try:
|
|
161
168
|
await asyncio.wait_for(proc.wait(), timeout=self._close_grace)
|
|
162
169
|
except TimeoutError:
|
|
163
|
-
self.
|
|
170
|
+
self._stop(proc, hard=True, job=self._job)
|
|
164
171
|
finally:
|
|
165
172
|
if self._reader_task:
|
|
166
173
|
self._reader_task.cancel()
|
|
167
174
|
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
168
175
|
await self._reader_task
|
|
169
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
|
|
170
181
|
self._fail_pending(BackendTransportError("backend closed"))
|
|
171
182
|
|
|
172
183
|
@staticmethod
|
|
173
|
-
def
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
def _stop(proc: asyncio.subprocess.Process, *, hard: bool, job: Any = None) -> None:
|
|
185
|
+
"""Stop ``proc`` by intent (soft: terminate, hard: kill). Never raises.
|
|
186
|
+
|
|
187
|
+
POSIX: SIGTERM/SIGKILL to the child's process group (it leads its own
|
|
188
|
+
via ``start_new_session=True``), reaching its descendants. Windows has
|
|
189
|
+
no ``signal.SIGKILL``/``os.killpg``/``os.getpgid``, so nothing
|
|
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()``.
|
|
180
192
|
"""
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
killpg = getattr(os, "killpg", None)
|
|
194
|
+
getpgid = getattr(os, "getpgid", None)
|
|
195
|
+
sig = getattr(signal, "SIGKILL" if hard else "SIGTERM", None)
|
|
196
|
+
if killpg is not None and getpgid is not None and sig is not None:
|
|
197
|
+
with contextlib.suppress(ProcessLookupError, PermissionError, OSError, AttributeError):
|
|
198
|
+
killpg(getpgid(proc.pid), sig)
|
|
199
|
+
return
|
|
200
|
+
if hard and sys.platform == "win32" and not (job is not None and _winjob.terminate(job)):
|
|
201
|
+
with contextlib.suppress(OSError, subprocess.SubprocessError):
|
|
202
|
+
subprocess.run(
|
|
203
|
+
["taskkill", "/F", "/T", "/PID", str(proc.pid)],
|
|
204
|
+
stdout=subprocess.DEVNULL,
|
|
205
|
+
stderr=subprocess.DEVNULL,
|
|
206
|
+
timeout=5,
|
|
207
|
+
check=False,
|
|
208
|
+
)
|
|
186
209
|
with contextlib.suppress(ProcessLookupError, OSError):
|
|
187
|
-
if
|
|
210
|
+
if hard:
|
|
188
211
|
proc.kill()
|
|
189
212
|
else:
|
|
190
213
|
proc.terminate()
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-workflow-sdk"
|
|
7
|
-
version = "0.6.
|
|
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"
|
|
@@ -24,6 +24,10 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import asyncio
|
|
26
26
|
import json
|
|
27
|
+
import os
|
|
28
|
+
import signal
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
27
31
|
|
|
28
32
|
import pytest
|
|
29
33
|
from agentflow.backends.base import AllowAll, DenyAll
|
|
@@ -303,6 +307,119 @@ async def test_close_terminates_then_kills_on_timeout(fake_proc, monkeypatch):
|
|
|
303
307
|
assert fake_proc.returncode == -9 # kill() was reached
|
|
304
308
|
|
|
305
309
|
|
|
310
|
+
async def test_close_on_windows_has_no_posix_signals(fake_proc, monkeypatch):
|
|
311
|
+
"""Windows shape: no signal.SIGKILL / os.killpg / os.getpgid. close() must not
|
|
312
|
+
raise AttributeError and must reap the tree with taskkill, then kill."""
|
|
313
|
+
backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
|
|
314
|
+
setup = asyncio.create_task(_drive_setup(fake_proc))
|
|
315
|
+
await backend.start()
|
|
316
|
+
await setup
|
|
317
|
+
fake_proc.pid = 4242
|
|
318
|
+
|
|
319
|
+
async def _never() -> int:
|
|
320
|
+
await asyncio.sleep(3600)
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
async def _fast_wait_for(coro, timeout):
|
|
324
|
+
coro.close()
|
|
325
|
+
raise TimeoutError
|
|
326
|
+
|
|
327
|
+
monkeypatch.setattr(fake_proc, "wait", _never)
|
|
328
|
+
monkeypatch.setattr(asyncio, "wait_for", _fast_wait_for)
|
|
329
|
+
monkeypatch.delattr(os, "killpg", raising=False)
|
|
330
|
+
monkeypatch.delattr(os, "getpgid", raising=False)
|
|
331
|
+
monkeypatch.delattr(signal, "SIGKILL", raising=False)
|
|
332
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
333
|
+
ran: list[list[str]] = []
|
|
334
|
+
monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: ran.append(cmd))
|
|
335
|
+
|
|
336
|
+
await backend.close()
|
|
337
|
+
|
|
338
|
+
assert ran == [["taskkill", "/F", "/T", "/PID", "4242"]]
|
|
339
|
+
assert fake_proc.returncode == -9
|
|
340
|
+
|
|
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
|
+
|
|
306
423
|
async def test_turn_rpc_failure_yields_error_event(fake_proc):
|
|
307
424
|
backend = KiroBackend("fake-agent", engine="v2", permission=AllowAll())
|
|
308
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"]
|
|
File without changes
|
|
File without changes
|
{agent_workflow_sdk-0.6.0 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|