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.
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/CHANGELOG.md +10 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/PKG-INFO +1 -1
- {agent_workflow_sdk-0.6.1 → 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.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/kiro.py +15 -6
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/pyproject.toml +1 -1
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_kiro_backend.py +81 -0
- agent_workflow_sdk-0.6.2/tests/test_winjob.py +83 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.env.example +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/ci.yml +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/docs.yml +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.github/workflows/release.yml +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/.gitignore +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/CONTRIBUTING.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/DESIGN.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/LICENSE +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/NOTICE +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/README.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/SECURITY.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/_pg.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/_http.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/anthropic.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/base.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/claude_code.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/cli_exec.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/codex.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/ollama.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/backends/openai.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/_serde.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/base.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/file.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/memory.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/postgres.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/redis.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/checkpoint/sqlite.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/compiled.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/memory.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/postgres.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/queue.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/records.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/registry.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/controlplane/worker.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/errors.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/events.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/graph.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/interrupts.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/observability.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/otel.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/budget.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/effects.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/gate.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/idempotent.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/loop.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/resilience.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/scoring.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/tool_loop.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prebuilt/watch.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/prometheus.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/py.typed +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/redaction.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/runtime.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/server/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/server/app.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/state.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/_util.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/base.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/memory.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/store/postgres.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/agentflow/telemetry.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/eval-harness.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/design-notes/token-budget.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/agent-backends.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/llm-backends.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/overview.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/permissions.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/backends/retries.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/context.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/execution.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/nodes-and-edges.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/running.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/state.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/concepts/subgraphs.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/checkpointing.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/control-plane.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/human-in-the-loop.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/observability.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/quality-gates.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/durability/store.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/installation.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/mental-model.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/overview.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/get-started/quickstart.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/index.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/patterns/long-running-loops.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/patterns/prebuilt.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/backends.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/budget.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/checkpoint.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/compiled.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/controlplane.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/errors.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/events.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/gate.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/graph.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/interrupts.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/observability.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/permissions.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/prebuilt.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/runtime.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/scoring.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/server.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/state.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/store.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api/watch.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/api.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/reference/glossary.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/index.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/01-first-graph.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/02-branching.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/03-parallel.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/04-dual-state.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/05-backend.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/06-gate.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/07-human.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/08-durability.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/09-watch.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/10-plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/optimizer/index.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/qa-agent.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/01-first-graph.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/02-branching.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/03-parallel.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/04-dual-state.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/05-agent.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/06-gate.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/07-human.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/08-durability.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/09-watch.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/10-plan-executor.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/docs/tutorials/query-optimizer/index.md +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/agents_demo.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/codebase_qa_ollama.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/dual_state_search.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/hello_graph.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/hitl_permission.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/idempotent_effect.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/mixed_backends.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/optimize_loop.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/plan_executor_demo.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/quality_gate.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/research_plan_implement_review.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/serve_control_plane.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/telemetry_demo.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tool_loop_ollama.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/01_first_graph.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/02_branching.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/03_parallel.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/04_dual_state.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/05_backend.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/06_gate.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/07_human.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/08_durability.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/09_watch.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/10_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/__init__.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch01_first_graph.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch02_branching.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch03_parallel.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch04_dual_state.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch05_agent.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch06_gate.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch07_human.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch08_durability.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch09_watch.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/ch10_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/db.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/proposer.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/tutorial/queryopt/scorer.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/examples/watch_until_done.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/mkdocs.yml +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/conftest.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_anthropic_backend.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_backend_lifecycle.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_budget.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_checkpoint_contract.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_cli_backends.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_concurrency.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_controlplane.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_effects.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_gate.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_graph_engine.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_hitl_permission.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_http_retry.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_idempotent.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_input_validation.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_live_backends.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_observability.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_ollama_backend.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_openai_backend.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_otel.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_permission_policy.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_plan_executor.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_runqueue.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_postgres_store.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_loop.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prebuilt_no_httpx.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_prometheus.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_provider_resilience.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_redaction.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_redis_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_reducers.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_resilience.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_run_timeout.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_scoring.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_server.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_sqlite_checkpointer.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_state_isolation.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_step_atomicity.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_store.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_subgraph.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_sync_facade.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_telemetry.py +0 -0
- {agent_workflow_sdk-0.6.1 → agent_workflow_sdk-0.6.2}/tests/test_tool_loop.py +0 -0
- {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.
|
|
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)
|
|
@@ -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
|
|
182
|
-
``taskkill /F /T``
|
|
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.
|
|
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"]
|
|
File without changes
|
|
File without changes
|
{agent_workflow_sdk-0.6.1 → 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
|
|
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
|