agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.whl
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.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""Explicitly started local Scheduler Lite engine.
|
|
2
|
+
|
|
3
|
+
There is intentionally no import-time loop and no Studio lifespan hook here.
|
|
4
|
+
Local schedules run only while a caller starts this engine; deployment-wide
|
|
5
|
+
always-on scheduling belongs to the Server/Operator phase, not this component.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
from collections.abc import Callable, Sequence
|
|
12
|
+
from datetime import datetime, timedelta, timezone
|
|
13
|
+
from typing import Protocol
|
|
14
|
+
from uuid import uuid4
|
|
15
|
+
|
|
16
|
+
from ksadk.scheduler.calendar import next_schedule_time
|
|
17
|
+
from ksadk.scheduler.contracts import ScheduledTask, ScheduleOccurrence
|
|
18
|
+
from ksadk.scheduler.sqlite_store import SchedulerSQLiteStore
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SchedulerEventReader(Protocol):
|
|
22
|
+
"""Read canonical SessionEvents for one accepted occurrence."""
|
|
23
|
+
|
|
24
|
+
async def read_events(self, occurrence: ScheduleOccurrence) -> Sequence[tuple[int, object]]: ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SchedulerDispatchReceipt(str):
|
|
28
|
+
"""A backward-compatible dispatcher result with an event-log cursor.
|
|
29
|
+
|
|
30
|
+
It remains a ``str`` so existing local dispatcher implementations keep
|
|
31
|
+
working. The new ``accepted_seq`` lets the scheduler resume from exactly
|
|
32
|
+
the AgentControl admission fact rather than guessing from wall-clock time.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
accepted_seq: int | None
|
|
36
|
+
|
|
37
|
+
def __new__(cls, command_id: str, *, accepted_seq: int | None = None):
|
|
38
|
+
value = super().__new__(cls, command_id)
|
|
39
|
+
value.accepted_seq = accepted_seq
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SchedulerDispatchError(RuntimeError):
|
|
44
|
+
"""A typed dispatcher failure safe to persist in occurrence history."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, code: str, detail: str) -> None:
|
|
47
|
+
super().__init__(detail)
|
|
48
|
+
self.code = code
|
|
49
|
+
self.detail = detail
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SchedulerDispatcher(Protocol):
|
|
53
|
+
"""The one execution seam. Implementations must submit AgentControl only."""
|
|
54
|
+
|
|
55
|
+
async def dispatch(self, task: ScheduledTask, occurrence: ScheduleOccurrence) -> str:
|
|
56
|
+
"""Return AgentControl's accepted command ID, or raise a typed error."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
Clock = Callable[[], datetime]
|
|
60
|
+
TickGuard = Callable[[], bool]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SchedulerEngine:
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
store: SchedulerSQLiteStore,
|
|
67
|
+
dispatcher: SchedulerDispatcher,
|
|
68
|
+
*,
|
|
69
|
+
owner_id: str | None = None,
|
|
70
|
+
clock: Clock | None = None,
|
|
71
|
+
tick_guard: TickGuard | None = None,
|
|
72
|
+
lease_seconds: int = 30,
|
|
73
|
+
) -> None:
|
|
74
|
+
self.store = store
|
|
75
|
+
self.dispatcher = dispatcher
|
|
76
|
+
self.owner_id = owner_id or f"studio-{uuid4().hex}"
|
|
77
|
+
self.clock = clock or (lambda: datetime.now(timezone.utc))
|
|
78
|
+
self.tick_guard = tick_guard
|
|
79
|
+
self.lease_seconds = lease_seconds
|
|
80
|
+
self._task: asyncio.Task[None] | None = None
|
|
81
|
+
self._stop = asyncio.Event()
|
|
82
|
+
self._poll_seconds: float | None = None
|
|
83
|
+
self._last_scan_at: datetime | None = None
|
|
84
|
+
self._last_scan_result: str | None = None
|
|
85
|
+
self._last_scan_detail: str | None = None
|
|
86
|
+
self._next_scan_at: datetime | None = None
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def running(self) -> bool:
|
|
90
|
+
return self._task is not None and not self._task.done()
|
|
91
|
+
|
|
92
|
+
def status(self) -> dict[str, object]:
|
|
93
|
+
"""Return a UI-safe snapshot of the local trigger process.
|
|
94
|
+
|
|
95
|
+
This is operational state, not a claim that every configured task is
|
|
96
|
+
executable. ``waiting_runtime`` deliberately means the monitor is
|
|
97
|
+
alive but will not claim due work until its trusted Kernel route is
|
|
98
|
+
available again.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"running": self.running,
|
|
103
|
+
"ownerId": self.owner_id,
|
|
104
|
+
"pollSeconds": self._poll_seconds,
|
|
105
|
+
"lastScanAt": self._last_scan_at,
|
|
106
|
+
"lastScanResult": self._last_scan_result,
|
|
107
|
+
"lastScanDetail": self._last_scan_detail,
|
|
108
|
+
"nextScanAt": self._next_scan_at,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async def tick(self) -> list[ScheduleOccurrence]:
|
|
112
|
+
"""Claim every due occurrence once and submit it through AgentControl."""
|
|
113
|
+
|
|
114
|
+
now = self.clock().astimezone(timezone.utc)
|
|
115
|
+
self._last_scan_at = now
|
|
116
|
+
if self.tick_guard is not None and not self.tick_guard():
|
|
117
|
+
self._last_scan_result = "waiting_runtime"
|
|
118
|
+
self._last_scan_detail = "agent_kernel_route_inactive"
|
|
119
|
+
return []
|
|
120
|
+
owns_lease = await asyncio.to_thread(
|
|
121
|
+
self.store.acquire_lease,
|
|
122
|
+
owner_id=self.owner_id,
|
|
123
|
+
now=now,
|
|
124
|
+
ttl_seconds=self.lease_seconds,
|
|
125
|
+
)
|
|
126
|
+
if not owns_lease:
|
|
127
|
+
self._last_scan_result = "lease_not_owned"
|
|
128
|
+
self._last_scan_detail = "another_local_scheduler_owns_the_lease"
|
|
129
|
+
return []
|
|
130
|
+
await self.reconcile()
|
|
131
|
+
due = await asyncio.to_thread(self.store.list_due, now)
|
|
132
|
+
result: list[ScheduleOccurrence] = []
|
|
133
|
+
for task, generation in due:
|
|
134
|
+
occurrence = await self._claim_scheduled(task, generation, now)
|
|
135
|
+
if occurrence is None:
|
|
136
|
+
continue
|
|
137
|
+
result.append(occurrence)
|
|
138
|
+
if occurrence.state == "skipped":
|
|
139
|
+
continue
|
|
140
|
+
try:
|
|
141
|
+
dispatch = await self.dispatcher.dispatch(task, occurrence)
|
|
142
|
+
except SchedulerDispatchError as exc:
|
|
143
|
+
result[-1] = await asyncio.to_thread(
|
|
144
|
+
self.store.finish,
|
|
145
|
+
occurrence.occurrence_id,
|
|
146
|
+
succeeded=False,
|
|
147
|
+
error_code=exc.code,
|
|
148
|
+
detail=exc.detail,
|
|
149
|
+
)
|
|
150
|
+
except Exception as exc: # noqa: BLE001 - normalize boundary errors
|
|
151
|
+
result[-1] = await asyncio.to_thread(
|
|
152
|
+
self.store.finish,
|
|
153
|
+
occurrence.occurrence_id,
|
|
154
|
+
succeeded=False,
|
|
155
|
+
error_code="DISPATCH_FAILED",
|
|
156
|
+
detail=str(exc)[:1024],
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
result[-1] = await asyncio.to_thread(
|
|
160
|
+
self.store.mark_accepted,
|
|
161
|
+
occurrence.occurrence_id,
|
|
162
|
+
str(dispatch),
|
|
163
|
+
accepted_seq=getattr(dispatch, "accepted_seq", None),
|
|
164
|
+
)
|
|
165
|
+
self._last_scan_result = "ok"
|
|
166
|
+
self._last_scan_detail = f"claimed={len(result)}"
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
async def _claim_scheduled(
|
|
170
|
+
self,
|
|
171
|
+
task: ScheduledTask,
|
|
172
|
+
generation: int,
|
|
173
|
+
now: datetime,
|
|
174
|
+
) -> ScheduleOccurrence | None:
|
|
175
|
+
assert task.next_run_at is not None
|
|
176
|
+
missed = task.next_run_at < now
|
|
177
|
+
next_run_at = next_schedule_time(
|
|
178
|
+
task.schedule,
|
|
179
|
+
after=now if missed else task.next_run_at,
|
|
180
|
+
anchor_at=task.created_at,
|
|
181
|
+
)
|
|
182
|
+
if missed and task.schedule.misfire_policy == "skip":
|
|
183
|
+
return await asyncio.to_thread(
|
|
184
|
+
self.store.claim_and_advance,
|
|
185
|
+
task,
|
|
186
|
+
generation=generation,
|
|
187
|
+
next_run_at=next_run_at,
|
|
188
|
+
state="skipped",
|
|
189
|
+
detail="misfire_skipped",
|
|
190
|
+
claimed_at=now,
|
|
191
|
+
)
|
|
192
|
+
if task.concurrency_policy == "forbid" and await asyncio.to_thread(
|
|
193
|
+
self.store.has_active_occurrence, task.task_id
|
|
194
|
+
):
|
|
195
|
+
return await asyncio.to_thread(
|
|
196
|
+
self.store.claim_and_advance,
|
|
197
|
+
task,
|
|
198
|
+
generation=generation,
|
|
199
|
+
next_run_at=next_run_at,
|
|
200
|
+
state="skipped",
|
|
201
|
+
detail="concurrency_forbid_active_occurrence",
|
|
202
|
+
claimed_at=now,
|
|
203
|
+
)
|
|
204
|
+
return await asyncio.to_thread(
|
|
205
|
+
self.store.claim_and_advance,
|
|
206
|
+
task,
|
|
207
|
+
generation=generation,
|
|
208
|
+
next_run_at=next_run_at,
|
|
209
|
+
claimed_at=now,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
async def run_now(self, task_id: str) -> ScheduleOccurrence:
|
|
213
|
+
"""Explicit user action; it still uses the dispatcher and durable log."""
|
|
214
|
+
|
|
215
|
+
value = await asyncio.to_thread(self.store.get_task, task_id)
|
|
216
|
+
if value is None:
|
|
217
|
+
raise KeyError(task_id)
|
|
218
|
+
task, generation = value
|
|
219
|
+
if not task.enabled:
|
|
220
|
+
raise SchedulerDispatchError("TASK_DISABLED", "scheduled task is disabled")
|
|
221
|
+
if task.concurrency_policy == "forbid" and await asyncio.to_thread(
|
|
222
|
+
self.store.has_active_occurrence, task.task_id
|
|
223
|
+
):
|
|
224
|
+
raise SchedulerDispatchError(
|
|
225
|
+
"CONCURRENCY_FORBID", "an earlier occurrence is still active"
|
|
226
|
+
)
|
|
227
|
+
# Run-now has a separate manual occurrence identity. It is intentionally
|
|
228
|
+
# not an implicit reschedule and does not advance the natural next time.
|
|
229
|
+
now = self.clock().astimezone(timezone.utc)
|
|
230
|
+
occurrence = await asyncio.to_thread(
|
|
231
|
+
self.store.claim_manual,
|
|
232
|
+
task,
|
|
233
|
+
generation=generation,
|
|
234
|
+
now=now,
|
|
235
|
+
)
|
|
236
|
+
if occurrence is None:
|
|
237
|
+
raise SchedulerDispatchError("TASK_CHANGED", "task changed while starting manually")
|
|
238
|
+
try:
|
|
239
|
+
dispatch = await self.dispatcher.dispatch(task, occurrence)
|
|
240
|
+
except SchedulerDispatchError as exc:
|
|
241
|
+
return await asyncio.to_thread(
|
|
242
|
+
self.store.finish,
|
|
243
|
+
occurrence.occurrence_id,
|
|
244
|
+
succeeded=False,
|
|
245
|
+
error_code=exc.code,
|
|
246
|
+
detail=exc.detail,
|
|
247
|
+
)
|
|
248
|
+
except Exception as exc: # noqa: BLE001
|
|
249
|
+
return await asyncio.to_thread(
|
|
250
|
+
self.store.finish,
|
|
251
|
+
occurrence.occurrence_id,
|
|
252
|
+
succeeded=False,
|
|
253
|
+
error_code="DISPATCH_FAILED",
|
|
254
|
+
detail=str(exc)[:1024],
|
|
255
|
+
)
|
|
256
|
+
return await asyncio.to_thread(
|
|
257
|
+
self.store.mark_accepted,
|
|
258
|
+
occurrence.occurrence_id,
|
|
259
|
+
str(dispatch),
|
|
260
|
+
accepted_seq=getattr(dispatch, "accepted_seq", None),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
async def reconcile(self) -> list[ScheduleOccurrence]:
|
|
264
|
+
"""Settle accepted work only from correlated canonical runtime facts.
|
|
265
|
+
|
|
266
|
+
``AgentControlReceipt.accepted`` means the command entered Inbox; it
|
|
267
|
+
is deliberately not a success signal. The control run-transition
|
|
268
|
+
event carries the originating command id, then the matching runtime
|
|
269
|
+
terminal event decides the immutable occurrence outcome.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
reader = getattr(self.dispatcher, "read_events", None)
|
|
273
|
+
if not callable(reader):
|
|
274
|
+
return []
|
|
275
|
+
changed: list[ScheduleOccurrence] = []
|
|
276
|
+
for occurrence in await asyncio.to_thread(self.store.list_active_occurrences):
|
|
277
|
+
if not occurrence.command_id or occurrence.target is None:
|
|
278
|
+
# Legacy local records lack the new immutable target snapshot.
|
|
279
|
+
# Preserve their accepted status rather than guessing a target.
|
|
280
|
+
continue
|
|
281
|
+
try:
|
|
282
|
+
events = await reader(occurrence)
|
|
283
|
+
except Exception: # noqa: BLE001 - retain accepted state for a later poll
|
|
284
|
+
continue
|
|
285
|
+
current = occurrence
|
|
286
|
+
for seq, envelope in events:
|
|
287
|
+
current = await self._reconcile_event(current, seq=seq, envelope=envelope)
|
|
288
|
+
if current.state in {"succeeded", "failed", "skipped", "cancelled"}:
|
|
289
|
+
break
|
|
290
|
+
if current != occurrence:
|
|
291
|
+
changed.append(current)
|
|
292
|
+
return changed
|
|
293
|
+
|
|
294
|
+
async def _reconcile_event(
|
|
295
|
+
self, occurrence: ScheduleOccurrence, *, seq: int, envelope: object
|
|
296
|
+
) -> ScheduleOccurrence:
|
|
297
|
+
event_type = str(getattr(envelope, "event_type", ""))
|
|
298
|
+
event_run_id = str(getattr(envelope, "run_id", "") or "")
|
|
299
|
+
causation_id = str(getattr(envelope, "causation_id", "") or "")
|
|
300
|
+
payload = getattr(envelope, "payload", {}) or {}
|
|
301
|
+
if not isinstance(payload, dict):
|
|
302
|
+
payload = {}
|
|
303
|
+
|
|
304
|
+
if event_type == "control.run_transition" and causation_id == occurrence.command_id:
|
|
305
|
+
run_id = event_run_id or str(payload.get("run_id") or "")
|
|
306
|
+
if run_id:
|
|
307
|
+
run_state = str(payload.get("state") or "")
|
|
308
|
+
if run_state not in {"running", "paused", "waiting"}:
|
|
309
|
+
return await asyncio.to_thread(
|
|
310
|
+
self.store.bind_run,
|
|
311
|
+
occurrence.occurrence_id,
|
|
312
|
+
run_id=run_id,
|
|
313
|
+
last_event_seq=seq,
|
|
314
|
+
)
|
|
315
|
+
return await asyncio.to_thread(
|
|
316
|
+
self.store.mark_running,
|
|
317
|
+
occurrence.occurrence_id,
|
|
318
|
+
run_id=run_id,
|
|
319
|
+
last_event_seq=seq,
|
|
320
|
+
)
|
|
321
|
+
if occurrence.run_id and event_run_id == occurrence.run_id:
|
|
322
|
+
if event_type == "run.completed":
|
|
323
|
+
return await asyncio.to_thread(
|
|
324
|
+
self.store.finish,
|
|
325
|
+
occurrence.occurrence_id,
|
|
326
|
+
succeeded=True,
|
|
327
|
+
detail="runtime_completed",
|
|
328
|
+
)
|
|
329
|
+
if event_type in {"run.failed", "run.canceled", "run.interrupted"}:
|
|
330
|
+
error = payload.get("error")
|
|
331
|
+
code = (
|
|
332
|
+
str(error.get("code") or "RUNTIME_FAILED")
|
|
333
|
+
if isinstance(error, dict)
|
|
334
|
+
else event_type.upper().replace(".", "_")
|
|
335
|
+
)
|
|
336
|
+
detail = (
|
|
337
|
+
str(error.get("message") or event_type)
|
|
338
|
+
if isinstance(error, dict)
|
|
339
|
+
else event_type
|
|
340
|
+
)
|
|
341
|
+
return await asyncio.to_thread(
|
|
342
|
+
self.store.finish,
|
|
343
|
+
occurrence.occurrence_id,
|
|
344
|
+
succeeded=False,
|
|
345
|
+
error_code=code[:128],
|
|
346
|
+
detail=detail[:1024],
|
|
347
|
+
)
|
|
348
|
+
return await asyncio.to_thread(
|
|
349
|
+
self.store.advance_reconciliation_cursor,
|
|
350
|
+
occurrence.occurrence_id,
|
|
351
|
+
last_event_seq=seq,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
async def settle(
|
|
355
|
+
self,
|
|
356
|
+
occurrence_id: str,
|
|
357
|
+
*,
|
|
358
|
+
succeeded: bool,
|
|
359
|
+
error_code: str | None = None,
|
|
360
|
+
detail: str | None = None,
|
|
361
|
+
) -> ScheduleOccurrence:
|
|
362
|
+
"""Called by the runtime-event reconciler, never by receipt handling."""
|
|
363
|
+
|
|
364
|
+
return await asyncio.to_thread(
|
|
365
|
+
self.store.finish,
|
|
366
|
+
occurrence_id,
|
|
367
|
+
succeeded=succeeded,
|
|
368
|
+
error_code=error_code,
|
|
369
|
+
detail=detail,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
async def start(self, *, poll_seconds: float = 5.0) -> None:
|
|
373
|
+
if poll_seconds <= 0:
|
|
374
|
+
raise ValueError("poll_seconds must be positive")
|
|
375
|
+
if self._task is not None and not self._task.done():
|
|
376
|
+
return
|
|
377
|
+
# asyncio primitives bind to the loop that first waits on them. Studio
|
|
378
|
+
# tests and embedded hosts may start/stop the same service through a
|
|
379
|
+
# fresh event loop, so never reuse the previous loop's Event.
|
|
380
|
+
self._stop = asyncio.Event()
|
|
381
|
+
self._poll_seconds = poll_seconds
|
|
382
|
+
|
|
383
|
+
async def loop() -> None:
|
|
384
|
+
while not self._stop.is_set():
|
|
385
|
+
try:
|
|
386
|
+
await self.tick()
|
|
387
|
+
except Exception as exc: # noqa: BLE001 - keep the monitor alive
|
|
388
|
+
self._last_scan_at = self.clock().astimezone(timezone.utc)
|
|
389
|
+
self._last_scan_result = "error"
|
|
390
|
+
self._last_scan_detail = f"{type(exc).__name__}: {exc}"[:512]
|
|
391
|
+
self._next_scan_at = self.clock().astimezone(timezone.utc) + timedelta(
|
|
392
|
+
seconds=poll_seconds
|
|
393
|
+
)
|
|
394
|
+
try:
|
|
395
|
+
await asyncio.wait_for(self._stop.wait(), timeout=poll_seconds)
|
|
396
|
+
except TimeoutError:
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
self._task = asyncio.create_task(loop(), name="ksadk-local-scheduler")
|
|
400
|
+
|
|
401
|
+
async def stop(self) -> None:
|
|
402
|
+
self._stop.set()
|
|
403
|
+
if self._task is None:
|
|
404
|
+
return
|
|
405
|
+
await self._task
|
|
406
|
+
self._task = None
|
|
407
|
+
self._next_scan_at = None
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
__all__ = [
|
|
411
|
+
"SchedulerDispatchError",
|
|
412
|
+
"SchedulerDispatchReceipt",
|
|
413
|
+
"SchedulerDispatcher",
|
|
414
|
+
"SchedulerEngine",
|
|
415
|
+
"SchedulerEventReader",
|
|
416
|
+
]
|