agentengine-sdk-python 0.8.4__py3-none-any.whl → 0.8.6__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.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/METADATA +17 -10
- agentengine_sdk_python-0.8.6.dist-info/RECORD +1731 -0
- ksadk/_async_iterators.py +79 -0
- ksadk/_build_provenance.json +1 -1
- ksadk/_model_anthropic.py +49 -0
- ksadk/_model_input.py +86 -0
- ksadk/_model_json.py +40 -0
- ksadk/_model_reasoning.py +17 -0
- ksadk/_model_refusal.py +20 -0
- ksadk/_model_sse.py +163 -0
- ksadk/_process.py +83 -0
- ksadk/_str_enum.py +17 -0
- ksadk/a2a/__init__.py +157 -167
- ksadk/a2a/_invocation_state.py +86 -0
- ksadk/a2a/_space_client_events.py +33 -22
- ksadk/a2a/adk.py +194 -0
- ksadk/a2a/adk_invocation_context.py +242 -0
- ksadk/a2a/adk_runtime.py +126 -0
- ksadk/a2a/adk_tools.py +319 -0
- ksadk/a2a/bootstrap.py +149 -23
- ksadk/a2a/build_validation.py +147 -0
- ksadk/a2a/card.py +23 -15
- ksadk/a2a/codex.py +160 -0
- ksadk/a2a/codex_checkpoint.py +111 -0
- ksadk/a2a/codex_recovery.py +96 -0
- ksadk/a2a/codex_recovery_events.py +123 -0
- ksadk/a2a/codex_runtime.py +198 -0
- ksadk/a2a/codex_session_checkpoint.py +162 -0
- ksadk/a2a/composition.py +88 -0
- ksadk/a2a/context_store.py +118 -19
- ksadk/a2a/control_plane.py +108 -106
- ksadk/a2a/event_adapter.py +92 -193
- ksadk/a2a/executor.py +257 -85
- ksadk/a2a/exposure.py +106 -0
- ksadk/a2a/external_transport.py +3 -4
- ksadk/a2a/identity.py +106 -12
- ksadk/a2a/ids.py +14 -3
- ksadk/a2a/inbound_kernel.py +133 -0
- ksadk/a2a/interaction_wire.py +76 -0
- ksadk/a2a/invocation_checkpoint.py +274 -0
- ksadk/a2a/invocation_policy.py +87 -0
- ksadk/a2a/invocation_publication.py +210 -0
- ksadk/a2a/invocation_service.py +972 -0
- ksadk/a2a/invocation_types.py +459 -0
- ksadk/a2a/kernel_task_adapter.py +259 -0
- ksadk/a2a/langgraph.py +20 -7
- ksadk/a2a/langgraph_invocation_context.py +136 -0
- ksadk/a2a/langgraph_managed.py +150 -0
- ksadk/a2a/langgraph_recovery.py +32 -0
- ksadk/a2a/langgraph_runtime.py +89 -0
- ksadk/a2a/langgraph_tool_runtime.py +132 -0
- ksadk/a2a/langgraph_tools.py +63 -0
- ksadk/a2a/message_digest.py +40 -0
- ksadk/a2a/platform.py +389 -0
- ksadk/a2a/policy_refs.py +13 -0
- ksadk/a2a/presentation.py +402 -0
- ksadk/a2a/product_kernel_facts.py +177 -0
- ksadk/a2a/product_key.py +99 -0
- ksadk/a2a/protocol_errors.py +116 -0
- ksadk/a2a/result_text.py +49 -0
- ksadk/a2a/resume_store.py +93 -3
- ksadk/a2a/routes.py +6 -1
- ksadk/a2a/runtime_approval.py +198 -0
- ksadk/a2a/runtime_input.py +151 -0
- ksadk/a2a/server.py +16 -7
- ksadk/a2a/space_client.py +101 -16
- ksadk/a2a/task_adapter.py +19 -5
- ksadk/a2a/task_event_dispatcher.py +39 -2
- ksadk/a2a/task_event_outbox.py +215 -0
- ksadk/a2a/task_event_outbox_owned.py +130 -0
- ksadk/a2a/task_store.py +37 -0
- ksadk/a2a/tool_activity.py +176 -0
- ksadk/a2a/workload_tokens.py +134 -0
- ksadk/a2ui/core.py +3 -3
- ksadk/api/client.py +38 -79
- ksadk/api/sse_stream.py +96 -0
- ksadk/builders/code_builder.py +121 -118
- ksadk/builders/container_builder.py +11 -29
- ksadk/builders/framework_requirements.py +22 -2
- ksadk/builders/managed_runtime_builder.py +12 -0
- ksadk/builders/source_provenance.py +87 -0
- ksadk/cli/__init__.py +36 -0
- ksadk/cli/cmd_a2a.py +43 -0
- ksadk/cli/cmd_build.py +9 -0
- ksadk/cli/cmd_config.py +4 -4
- ksadk/cli/cmd_create.py +12 -12
- ksadk/cli/cmd_deploy.py +27 -2
- ksadk/cli/cmd_hermes.py +24 -2
- ksadk/cli/cmd_invoke.py +64 -88
- ksadk/cli/cmd_openclaw.py +26 -1
- ksadk/cli/cmd_plugin.py +70 -12
- ksadk/cli/cmd_studio.py +69 -17
- ksadk/cli/cmd_web.py +2 -0
- ksadk/cli/hosted_invoke.py +206 -0
- ksadk/cli/invoke_content.py +60 -0
- ksadk/cli/openclaw_env.py +1 -1
- ksadk/cli/resource_common.py +26 -0
- ksadk/cli/runtime_bootstrap.py +27 -5
- ksadk/codex/_expert_request_reader.py +48 -0
- ksadk/codex/a2a_tools.py +140 -0
- ksadk/codex/async_approval.py +169 -0
- ksadk/codex/async_client.py +938 -0
- ksadk/codex/async_proxy.py +216 -0
- ksadk/codex/base_runtime.py +25 -0
- ksadk/codex/client.py +3 -1047
- ksadk/codex/client_payloads.py +85 -0
- ksadk/codex/rollout_checkpoint.py +264 -0
- ksadk/codex/runtime.py +290 -54
- ksadk/common/auth.py +2 -2
- ksadk/common/cloud_headers.py +36 -0
- ksadk/common/diagnostics.py +34 -0
- ksadk/common/kop_client.py +2 -1
- ksadk/configs/__init__.py +1 -1
- ksadk/configs/env_registry.py +216 -143
- ksadk/configs/env_registry_a2a.py +173 -0
- ksadk/configs/env_registry_studio.py +134 -0
- ksadk/configs/global_config.py +2 -1
- ksadk/configs/settings.py +9 -3
- ksadk/connector/__init__.py +17 -0
- ksadk/connector/channel.py +477 -0
- ksadk/context_engine/assembler.py +9 -0
- ksadk/context_engine/capabilities.py +52 -4
- ksadk/context_engine/contributors.py +80 -17
- ksadk/context_engine/instruction_authority.py +18 -0
- ksadk/conversations/attachment_storage.py +10 -3
- ksadk/conversations/attachments.py +69 -20
- ksadk/conversations/contracts.py +28 -9
- ksadk/conversations/message_projection.py +51 -155
- ksadk/conversations/message_projection_blocks.py +137 -0
- ksadk/conversations/model_context.py +11 -3
- ksadk/conversations/normalize.py +28 -10
- ksadk/conversations/projector.py +63 -0
- ksadk/conversations/runtime_baseline.py +37 -0
- ksadk/conversations/runtime_compaction.py +7 -2
- ksadk/conversations/runtime_input.py +16 -4
- ksadk/conversations/runtime_invocation.py +17 -4
- ksadk/conversations/runtime_observability.py +39 -10
- ksadk/conversations/runtime_payloads.py +12 -0
- ksadk/conversations/runtime_persistence.py +296 -32
- ksadk/conversations/runtime_preparation.py +98 -13
- ksadk/conversations/runtime_projection.py +217 -0
- ksadk/conversations/runtime_resume.py +141 -70
- ksadk/conversations/runtime_stream_events.py +29 -35
- ksadk/conversations/runtime_streaming.py +8 -0
- ksadk/conversations/tool_receipt_resume.py +387 -0
- ksadk/conversations/tool_receipts.py +32 -0
- ksadk/deployment/env_forward.py +5 -0
- ksadk/deployment/manager.py +15 -6
- ksadk/deployment/providers/serverless.py +76 -136
- ksadk/deployment/providers/serverless_payloads.py +122 -0
- ksadk/evaluation/agent_eval_client.py +2 -1
- ksadk/evaluation/contracts.py +78 -3
- ksadk/evaluation/evaluators.py +166 -1
- ksadk/evaluation/evidence.py +271 -4
- ksadk/evaluation/local_adapter.py +76 -12
- ksadk/evaluation/studio_build_adapter.py +73 -9
- ksadk/events/adapters/_a2a_checkpoint.py +151 -0
- ksadk/events/adapters/_a2a_snapshot.py +18 -14
- ksadk/events/adapters/_a2a_support.py +15 -3
- ksadk/events/adapters/_a2a_tool_activity.py +123 -0
- ksadk/events/adapters/_codex_errors.py +49 -0
- ksadk/events/adapters/_codex_items.py +16 -8
- ksadk/events/adapters/_langgraph_support.py +46 -1
- ksadk/events/adapters/a2a.py +54 -26
- ksadk/events/adapters/adk.py +188 -30
- ksadk/events/adapters/codex.py +33 -7
- ksadk/events/adapters/langgraph.py +16 -6
- ksadk/events/canonical.py +5 -0
- ksadk/events/canonical_store.py +200 -4
- ksadk/events/reducer.py +82 -0
- ksadk/events/run_authority.py +176 -0
- ksadk/events/session_event.py +2 -2
- ksadk/harness/__init__.py +18 -0
- ksadk/harness/app.py +11 -0
- ksadk/harness/artifact_adapters/__init__.py +1 -0
- ksadk/harness/artifact_adapters/native_page_budget.py +172 -0
- ksadk/harness/artifact_adapters/native_search.py +249 -0
- ksadk/harness/artifact_adapters/native_search_budget.py +134 -0
- ksadk/harness/artifact_adapters/native_store.py +115 -0
- ksadk/harness/artifact_adapters/native_tool.py +222 -0
- ksadk/harness/artifact_store.py +230 -0
- ksadk/harness/browser_adapters/__init__.py +6 -0
- ksadk/harness/browser_adapters/native_browser_tool.py +92 -0
- ksadk/harness/browser_adapters/native_dom_snapshot.py +187 -0
- ksadk/harness/browser_adapters/native_playwright.py +953 -0
- ksadk/harness/browser_adapters/native_vision.py +295 -0
- ksadk/harness/capabilities.py +115 -0
- ksadk/harness/capability_adapters/__init__.py +1 -0
- ksadk/harness/capability_adapters/native_dsh.py +289 -0
- ksadk/harness/capability_runtime.py +332 -0
- ksadk/harness/closure_contract.py +92 -0
- ksadk/harness/compaction_record.py +153 -0
- ksadk/harness/compiler.py +284 -0
- ksadk/harness/config.py +28 -5
- ksadk/harness/conformance/__init__.py +33 -0
- ksadk/harness/conformance/contract.py +504 -0
- ksadk/harness/conformance/fixtures.py +102 -0
- ksadk/harness/context_engine.py +388 -0
- ksadk/harness/context_maintenance.py +259 -0
- ksadk/harness/context_manifest.py +188 -0
- ksadk/harness/context_sections.py +397 -0
- ksadk/harness/context_tuning.py +168 -0
- ksadk/harness/diagnostic_adapters/__init__.py +1 -0
- ksadk/harness/diagnostic_adapters/contracts.py +70 -0
- ksadk/harness/diagnostic_adapters/lsp_rpc.py +210 -0
- ksadk/harness/diagnostic_adapters/native_lsp.py +286 -0
- ksadk/harness/draft_runtime.py +322 -0
- ksadk/harness/engine/__init__.py +40 -0
- ksadk/harness/engine/base.py +122 -0
- ksadk/harness/engine/budgets.py +108 -0
- ksadk/harness/engine/capability_declarations.py +59 -0
- ksadk/harness/engine/completion_guard.py +57 -0
- ksadk/harness/engine/context_pipeline.py +1351 -0
- ksadk/harness/engine/graph_builder.py +352 -0
- ksadk/harness/engine/hooked_reasoner.py +380 -0
- ksadk/harness/engine/langgraph.py +1049 -0
- ksadk/harness/engine/mcp_disclosure.py +701 -0
- ksadk/harness/engine/phases/__init__.py +6 -0
- ksadk/harness/engine/phases/context.py +48 -0
- ksadk/harness/engine/phases/interaction.py +31 -0
- ksadk/harness/engine/phases/reason.py +552 -0
- ksadk/harness/engine/phases/tools.py +638 -0
- ksadk/harness/engine/policy_runtime.py +81 -0
- ksadk/harness/engine/postgres_checkpointer.py +386 -0
- ksadk/harness/engine/run_control_bridge.py +100 -0
- ksadk/harness/engine/run_types.py +98 -0
- ksadk/harness/engine/skill_disclosure.py +275 -0
- ksadk/harness/engine/spans.py +41 -0
- ksadk/harness/engine/steering.py +177 -0
- ksadk/harness/engine/subagents.py +360 -0
- ksadk/harness/engine/thread_ids.py +112 -0
- ksadk/harness/engine/tool_dispatch.py +249 -0
- ksadk/harness/event_tree.py +212 -0
- ksadk/harness/events.py +498 -0
- ksadk/harness/execution_policy.py +104 -0
- ksadk/harness/hooks.py +259 -0
- ksadk/harness/insights.py +210 -0
- ksadk/harness/lifecycle.py +578 -0
- ksadk/harness/lifecycle_conformance.py +233 -0
- ksadk/harness/lifecycle_local_plane.py +104 -0
- ksadk/harness/loop/__init__.py +44 -0
- ksadk/harness/loop/reason.py +699 -0
- ksadk/harness/loop/tools.py +907 -0
- ksadk/harness/managed_runtime.py +1044 -0
- ksadk/harness/mcp_disclosure_contract.py +85 -0
- ksadk/harness/mcp_matrix.py +474 -0
- ksadk/harness/mcp_result_policy.py +44 -0
- ksadk/harness/mcp_runtime.py +504 -0
- ksadk/harness/memory_consolidation.py +157 -0
- ksadk/harness/memory_loop.py +156 -0
- ksadk/harness/memory_runtime.py +541 -0
- ksadk/harness/metrics.py +90 -0
- ksadk/harness/micro_compact.py +69 -0
- ksadk/harness/model_adapters/__init__.py +1 -0
- ksadk/harness/model_adapters/native_mcp_media.py +100 -0
- ksadk/harness/model_adapters/native_mcp_result.py +36 -0
- ksadk/harness/model_adapters/native_openai.py +703 -0
- ksadk/harness/model_capability.py +468 -0
- ksadk/harness/model_provider.py +430 -0
- ksadk/harness/multimodal.py +57 -0
- ksadk/harness/native/__init__.py +1 -0
- ksadk/harness/native/activation_contract.py +25 -0
- ksadk/harness/native/activation_recovery.py +29 -0
- ksadk/harness/native/adapters.py +291 -0
- ksadk/harness/native/artifact_contract.py +14 -0
- ksadk/harness/native/artifact_partial_read_capacity.py +171 -0
- ksadk/harness/native/artifact_read_capacity.py +182 -0
- ksadk/harness/native/artifact_read_contract.py +27 -0
- ksadk/harness/native/artifact_runtime.py +408 -0
- ksadk/harness/native/artifact_search_capacity.py +206 -0
- ksadk/harness/native/artifact_search_contract.py +40 -0
- ksadk/harness/native/artifact_single_read_capacity.py +170 -0
- ksadk/harness/native/browser_contract.py +229 -0
- ksadk/harness/native/browser_recovery.py +183 -0
- ksadk/harness/native/capabilities.py +204 -0
- ksadk/harness/native/child_approval.py +60 -0
- ksadk/harness/native/child_budget.py +136 -0
- ksadk/harness/native/child_cancellation.py +80 -0
- ksadk/harness/native/child_input.py +98 -0
- ksadk/harness/native/child_input_runtime.py +59 -0
- ksadk/harness/native/child_interruption.py +81 -0
- ksadk/harness/native/child_lifecycle.py +246 -0
- ksadk/harness/native/child_lifecycle_contract.py +91 -0
- ksadk/harness/native/child_notifications.py +75 -0
- ksadk/harness/native/child_promotion.py +66 -0
- ksadk/harness/native/command_runtime.py +99 -0
- ksadk/harness/native/completion.py +302 -0
- ksadk/harness/native/completion_text.py +116 -0
- ksadk/harness/native/context.py +358 -0
- ksadk/harness/native/context_artifact_page.py +75 -0
- ksadk/harness/native/context_compaction_projection.py +131 -0
- ksadk/harness/native/context_contributions.py +320 -0
- ksadk/harness/native/context_contributor_contract.py +133 -0
- ksadk/harness/native/context_driver.py +820 -0
- ksadk/harness/native/context_fresh_tools.py +49 -0
- ksadk/harness/native/context_history.py +272 -0
- ksadk/harness/native/context_microcompact.py +61 -0
- ksadk/harness/native/context_partial_summary.py +58 -0
- ksadk/harness/native/context_partition.py +492 -0
- ksadk/harness/native/context_pruning.py +275 -0
- ksadk/harness/native/context_reduction.py +60 -0
- ksadk/harness/native/context_replay.py +148 -0
- ksadk/harness/native/context_request_projection.py +526 -0
- ksadk/harness/native/context_runtime.py +976 -0
- ksadk/harness/native/context_runtime_contracts.py +409 -0
- ksadk/harness/native/context_search.py +112 -0
- ksadk/harness/native/context_source_fragments.py +188 -0
- ksadk/harness/native/context_source_progress.py +119 -0
- ksadk/harness/native/context_summary_planning.py +788 -0
- ksadk/harness/native/context_summary_recovery.py +105 -0
- ksadk/harness/native/delivery_review.py +331 -0
- ksadk/harness/native/delivery_review_contract.py +54 -0
- ksadk/harness/native/effect_admission.py +82 -0
- ksadk/harness/native/effect_control.py +318 -0
- ksadk/harness/native/effect_receipts.py +729 -0
- ksadk/harness/native/effect_runtime.py +303 -0
- ksadk/harness/native/effective_configuration.py +28 -0
- ksadk/harness/native/engine.py +945 -0
- ksadk/harness/native/final_output.py +34 -0
- ksadk/harness/native/final_submission.py +58 -0
- ksadk/harness/native/finalization_workspace.py +96 -0
- ksadk/harness/native/fork.py +151 -0
- ksadk/harness/native/guidance.py +91 -0
- ksadk/harness/native/history.py +381 -0
- ksadk/harness/native/hook_executor.py +205 -0
- ksadk/harness/native/hook_runtime_contract.py +282 -0
- ksadk/harness/native/interaction_observation.py +117 -0
- ksadk/harness/native/lease.py +103 -0
- ksadk/harness/native/machine.py +974 -0
- ksadk/harness/native/mcp_result_governance.py +156 -0
- ksadk/harness/native/model.py +570 -0
- ksadk/harness/native/model_feedback.py +63 -0
- ksadk/harness/native/model_input.py +30 -0
- ksadk/harness/native/model_recovery_contract.py +35 -0
- ksadk/harness/native/model_replay.py +128 -0
- ksadk/harness/native/model_request.py +133 -0
- ksadk/harness/native/model_runtime.py +196 -0
- ksadk/harness/native/model_takeover.py +419 -0
- ksadk/harness/native/model_tool_batch.py +40 -0
- ksadk/harness/native/observed_progress.py +46 -0
- ksadk/harness/native/operator_guidance.py +201 -0
- ksadk/harness/native/output_contract.py +80 -0
- ksadk/harness/native/output_recovery.py +24 -0
- ksadk/harness/native/ports.py +749 -0
- ksadk/harness/native/precompact_hook_runtime.py +171 -0
- ksadk/harness/native/replay_context.py +149 -0
- ksadk/harness/native/request_media.py +112 -0
- ksadk/harness/native/restore_contract.py +16 -0
- ksadk/harness/native/run_clock.py +120 -0
- ksadk/harness/native/state.py +436 -0
- ksadk/harness/native/steering.py +170 -0
- ksadk/harness/native/stop_hook_runtime.py +151 -0
- ksadk/harness/native/studio_admission.py +29 -0
- ksadk/harness/native/subagents.py +86 -0
- ksadk/harness/native/task_policy.py +99 -0
- ksadk/harness/native/task_progress.py +86 -0
- ksadk/harness/native/terminal_verification.py +43 -0
- ksadk/harness/native/tool_argument_feedback.py +178 -0
- ksadk/harness/native/tool_budget.py +253 -0
- ksadk/harness/native/tool_error_streak.py +89 -0
- ksadk/harness/native/tool_hook_runtime.py +343 -0
- ksadk/harness/native/tool_input_validation.py +58 -0
- ksadk/harness/native/tool_interruption.py +96 -0
- ksadk/harness/native/tool_observation.py +259 -0
- ksadk/harness/native/tool_progress.py +223 -0
- ksadk/harness/native/tool_recovery.py +25 -0
- ksadk/harness/native/tool_scheduling.py +33 -0
- ksadk/harness/native/tool_schema.py +126 -0
- ksadk/harness/native/transitions.py +151 -0
- ksadk/harness/native/turn_control.py +98 -0
- ksadk/harness/native/user_document.py +98 -0
- ksadk/harness/native/user_document_capture.py +49 -0
- ksadk/harness/native/user_input.py +87 -0
- ksadk/harness/native/verification.py +432 -0
- ksadk/harness/native/verification_binding.py +40 -0
- ksadk/harness/native/verification_runtime.py +233 -0
- ksadk/harness/native/verification_workspace.py +67 -0
- ksadk/harness/native_driver_failure.py +123 -0
- ksadk/harness/native_facade.py +977 -0
- ksadk/harness/native_factory.py +244 -0
- ksadk/harness/native_projection.py +503 -0
- ksadk/harness/native_runtime.py +621 -0
- ksadk/harness/native_shadow.py +250 -0
- ksadk/harness/native_shadow_inputs.py +146 -0
- ksadk/harness/observability/__init__.py +29 -0
- ksadk/harness/observability/inspection.py +528 -0
- ksadk/harness/observability/metrics.py +167 -0
- ksadk/harness/observability/otel_exporter.py +218 -0
- ksadk/harness/progress_adapters/__init__.py +5 -0
- ksadk/harness/progress_adapters/native_todo.py +75 -0
- ksadk/harness/prompt_cache.py +132 -0
- ksadk/harness/public_activity.py +213 -0
- ksadk/harness/public_output.py +30 -0
- ksadk/harness/public_summary.py +36 -0
- ksadk/harness/readiness.py +249 -0
- ksadk/harness/reasoner.py +779 -17
- ksadk/harness/release_readiness.py +140 -0
- ksadk/harness/resource_ref.py +69 -0
- ksadk/harness/rpc.py +200 -0
- ksadk/harness/run_control.py +773 -0
- ksadk/harness/runtime.py +157 -113
- ksadk/harness/runtime_capabilities.py +228 -0
- ksadk/harness/runtime_server.py +514 -0
- ksadk/harness/sandbox_adapters.py +739 -0
- ksadk/harness/sandbox_backend.py +733 -0
- ksadk/harness/sandbox_conformance.py +288 -0
- ksadk/harness/sandbox_lease.py +77 -0
- ksadk/harness/sandbox_lease_conformance.py +259 -0
- ksadk/harness/sandbox_matrix.py +170 -0
- ksadk/harness/sandbox_reconciliation.py +140 -0
- ksadk/harness/sandbox_recovery.py +586 -0
- ksadk/harness/sandbox_recovery_conformance.py +246 -0
- ksadk/harness/sdk.py +153 -0
- ksadk/harness/session_store.py +589 -0
- ksadk/harness/skill_adapters/__init__.py +1 -0
- ksadk/harness/skill_adapters/native_bundle.py +148 -0
- ksadk/harness/skill_adapters/native_disclosure.py +306 -0
- ksadk/harness/skill_composition.py +212 -0
- ksadk/harness/skill_feedback.py +123 -0
- ksadk/harness/skill_intent.py +47 -0
- ksadk/harness/skill_matrix.py +233 -0
- ksadk/harness/skill_runtime.py +317 -0
- ksadk/harness/spec.py +342 -0
- ksadk/harness/state.py +155 -0
- ksadk/harness/stores/__init__.py +14 -0
- ksadk/harness/stores/native_command_settlement.py +65 -0
- ksadk/harness/stores/native_contract.py +311 -0
- ksadk/harness/stores/native_memory.py +326 -0
- ksadk/harness/stores/native_memory_archive.py +70 -0
- ksadk/harness/stores/native_migration_cli.py +91 -0
- ksadk/harness/stores/native_outbox.py +56 -0
- ksadk/harness/stores/native_sqlite.py +782 -0
- ksadk/harness/stores/native_sqlite_audit.py +29 -0
- ksadk/harness/stores/native_sqlite_migration.py +252 -0
- ksadk/harness/strategies.py +149 -0
- ksadk/harness/subagent.py +263 -0
- ksadk/harness/subagent_adapters/checkpoint_recovery.py +120 -0
- ksadk/harness/subagent_adapters/native_background_child.py +803 -0
- ksadk/harness/subagent_adapters/native_child.py +819 -0
- ksadk/harness/subagent_adapters/native_child_approval.py +128 -0
- ksadk/harness/subagent_adapters/native_child_continuation.py +51 -0
- ksadk/harness/subagent_adapters/native_child_result.py +392 -0
- ksadk/harness/subagent_adapters/prepared_recovery.py +120 -0
- ksadk/harness/tool_display.py +75 -0
- ksadk/harness/tool_display_contract.py +134 -0
- ksadk/harness/tool_output.py +141 -0
- ksadk/harness/tool_policy.py +134 -0
- ksadk/harness/tool_receipts.py +96 -0
- ksadk/harness/tool_reliability.py +81 -0
- ksadk/harness/tool_result_policy.py +216 -0
- ksadk/harness/tools.py +52 -0
- ksadk/harness/user_errors.py +69 -0
- ksadk/harness/working_context.py +81 -0
- ksadk/harness/working_context_patch.py +99 -0
- ksadk/harness/workspace_adapters/__init__.py +6 -0
- ksadk/harness/workspace_adapters/native_directory.py +180 -0
- ksadk/harness/workspace_adapters/native_git.py +208 -0
- ksadk/harness/workspace_adapters/native_isolation.py +350 -0
- ksadk/identity/resolver.py +18 -0
- ksadk/interaction/__init__.py +1 -1
- ksadk/interaction/ledger.py +1 -1
- ksadk/interaction/provider.py +1 -1
- ksadk/interaction/providers/__init__.py +3 -1
- ksadk/interaction/providers/adk.py +1 -1
- ksadk/interaction/providers/codex.py +1 -1
- ksadk/interaction/providers/harness.py +50 -0
- ksadk/interaction/providers/langgraph.py +9 -2
- ksadk/kernel/__init__.py +10 -0
- ksadk/kernel/approval_presentation.py +91 -0
- ksadk/kernel/authorization.py +1 -1
- ksadk/kernel/bootstrap.py +220 -252
- ksadk/kernel/contract_fingerprints.py +45 -1
- ksadk/kernel/contracts.py +30 -0
- ksadk/kernel/control.py +180 -29
- ksadk/kernel/execution_grants.py +428 -0
- ksadk/kernel/execution_grants_memory.py +252 -0
- ksadk/kernel/execution_grants_postgres.py +395 -0
- ksadk/kernel/execution_grants_sqlite.py +435 -0
- ksadk/kernel/execution_host_ingress.py +1071 -0
- ksadk/kernel/history_projection.py +120 -0
- ksadk/kernel/ingress.py +139 -226
- ksadk/kernel/ingress_bootstrap.py +268 -0
- ksadk/kernel/legacy_history.py +179 -0
- ksadk/kernel/mapping.py +1 -1
- ksadk/kernel/memory_store.py +61 -58
- ksadk/kernel/memory_store_projection.py +63 -0
- ksadk/kernel/postgres_event_log.py +181 -0
- ksadk/kernel/postgres_store.py +230 -231
- ksadk/kernel/readiness.py +125 -0
- ksadk/kernel/recovery.py +166 -16
- ksadk/kernel/recovery_projection.py +124 -0
- ksadk/kernel/runtime_checkpoint.py +34 -0
- ksadk/kernel/sql/001_agent_kernel.sql +28 -0
- ksadk/kernel/sqlite_schema.py +100 -0
- ksadk/kernel/sqlite_store.py +410 -200
- ksadk/kernel/state.py +2 -3
- ksadk/kernel/store.py +104 -5
- ksadk/kernel/teams_artifact_tools.py +392 -0
- ksadk/kernel/teams_effects.py +851 -0
- ksadk/kernel/teams_execution_context.py +408 -0
- ksadk/kernel/teams_host_http.py +237 -0
- ksadk/kernel/teams_material_transport.py +245 -0
- ksadk/kernel/teams_materials.py +318 -0
- ksadk/kernel/teams_runtime_bootstrap.py +604 -0
- ksadk/kernel/teams_tool_runtime.py +273 -0
- ksadk/kernel/teams_workspace.py +259 -0
- ksadk/kernel/worker.py +369 -340
- ksadk/kernel/worker_controls.py +454 -0
- ksadk/kernel/worker_history.py +72 -0
- ksadk/kernel/worker_identity.py +74 -0
- ksadk/kernel/worker_interactions.py +197 -0
- ksadk/knowledge_base/client.py +15 -1
- ksadk/managed_runtime.py +29 -2
- ksadk/memory/adk/backends/http_ltm_backend.py +2 -1
- ksadk/memory/adk/backends/sdk_ltm_backend.py +15 -1
- ksadk/memory/adk/bounded_database_session_service.py +232 -0
- ksadk/memory/adk/resilient_session_service.py +115 -15
- ksadk/memory/adk/short_term_memory.py +8 -9
- ksadk/memory/coordinator.py +22 -6
- ksadk/memory/extraction.py +159 -7
- ksadk/memory/models.py +77 -1
- ksadk/memory/provider_resolver.py +5 -0
- ksadk/memory/providers/local_candidates.py +183 -0
- ksadk/memory/providers/local_postgres.py +200 -0
- ksadk/memory/providers/local_sqlite.py +280 -67
- ksadk/memory/resolved_policy.py +12 -7
- ksadk/memory/retrieval.py +189 -0
- ksadk/memory/store_adapter.py +150 -0
- ksadk/model_gateway/__init__.py +29 -0
- ksadk/model_gateway/client.py +253 -0
- ksadk/model_gateway/events.py +113 -0
- ksadk/model_gateway/matrix.py +297 -0
- ksadk/model_policy.py +2 -2
- ksadk/model_proxy/compat.py +670 -0
- ksadk/model_proxy/detect.py +254 -0
- ksadk/model_proxy/server.py +14 -9
- ksadk/model_proxy/transform.py +72 -6
- ksadk/plugins/__init__.py +1 -1
- ksadk/plugins/bridges/dsh.py +29 -20
- ksadk/plugins/bridges/dsh_profile_manifest.py +36 -0
- ksadk/plugins/builtin_resources.py +199 -0
- ksadk/plugins/builtins.py +417 -185
- ksadk/plugins/companion_artifacts.py +83 -0
- ksadk/plugins/companions.py +368 -0
- ksadk/plugins/context_contributor.py +15 -4
- ksadk/plugins/contracts.py +1 -1
- ksadk/plugins/dsh_channels.py +156 -0
- ksadk/plugins/dsh_dependency_policy.py +41 -0
- ksadk/plugins/dsh_home.py +140 -0
- ksadk/plugins/dsh_teams.py +105 -0
- ksadk/plugins/dsh_toolchain.py +92 -21
- ksadk/plugins/execution_host.py +107 -0
- ksadk/plugins/host.py +73 -5
- ksadk/plugins/kernel_adapter.py +44 -1
- ksadk/plugins/memory_provider.py +180 -0
- ksadk/plugins/providers/_process_group.py +50 -0
- ksadk/plugins/providers/bundles/dsh-channels-client/client.js +19 -0
- ksadk/plugins/providers/bundles/dsh-channels-client/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-channels-client/index.mjs +7 -0
- ksadk/plugins/providers/bundles/dsh-channels-client/package.json +12 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +24 -2
- ksadk/plugins/providers/bundles/dsh-knowledge/package.json +1 -1
- ksadk/plugins/providers/bundles/dsh-memory/package.json +1 -1
- ksadk/plugins/providers/bundles/dsh-skill-center/package.json +1 -1
- ksadk/plugins/providers/bundles/dsh-teams-client/client.js +29 -0
- ksadk/plugins/providers/bundles/dsh-teams-client/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-client/index.mjs +11 -0
- ksadk/plugins/providers/bundles/dsh-teams-client/package.json +35 -0
- ksadk/plugins/providers/bundles/dsh-teams-leader-policy/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-leader-policy/index.mjs +13 -0
- ksadk/plugins/providers/bundles/dsh-teams-leader-policy/package.json +27 -0
- ksadk/plugins/providers/bundles/dsh-teams-profile/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-profile/index.mjs +12 -0
- ksadk/plugins/providers/bundles/dsh-teams-profile/package.json +26 -0
- ksadk/plugins/providers/bundles/dsh-teams-router/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-router/index.mjs +16 -0
- ksadk/plugins/providers/bundles/dsh-teams-router/package.json +27 -0
- ksadk/plugins/providers/bundles/dsh-teams-tasks/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-tasks/index.mjs +16 -0
- ksadk/plugins/providers/bundles/dsh-teams-tasks/package.json +27 -0
- ksadk/plugins/providers/bundles/dsh-teams-tools/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-teams-tools/index.mjs +25 -0
- ksadk/plugins/providers/bundles/dsh-teams-tools/package.json +29 -0
- ksadk/plugins/providers/bundles/dsh-teams-tools/tools.json +235 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +1 -1
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +51 -1
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +2 -2
- ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/client.mjs +133 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/index.mjs +14 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +27 -4
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +9 -5
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +1 -1
- ksadk/plugins/providers/codex.py +52 -40
- ksadk/plugins/providers/codex_native.py +20 -0
- ksadk/plugins/providers/codex_skills.py +46 -0
- ksadk/plugins/providers/dsh_capabilities.py +49 -143
- ksadk/plugins/providers/dsh_connector_lease.py +225 -0
- ksadk/plugins/providers/dsh_mcp.py +163 -1
- ksadk/plugins/providers/harness.py +413 -52
- ksadk/plugins/providers/harness_context.py +61 -0
- ksadk/plugins/providers/harness_contributions.py +241 -0
- ksadk/plugins/providers/harness_delegation.py +670 -0
- ksadk/plugins/providers/harness_hooks.py +121 -0
- ksadk/plugins/providers/harness_managed.py +550 -0
- ksadk/plugins/providers/harness_tools.py +455 -0
- ksadk/plugins/providers/legacy_catalog.py +1 -1
- ksadk/plugins/providers/mcp_projection.py +100 -3
- ksadk/plugins/providers/platform_resources.py +16 -6
- ksadk/plugins/providers/shipped_dsh.py +4 -1
- ksadk/plugins/subagent_providers/codex.py +28 -6
- ksadk/plugins/teams/__init__.py +5 -0
- ksadk/plugins/teams/api.py +405 -0
- ksadk/plugins/teams/application.py +540 -0
- ksadk/plugins/teams/artifacts.py +60 -0
- ksadk/plugins/teams/build_artifacts.py +460 -0
- ksadk/plugins/teams/cloud_contract_fingerprints.py +22 -0
- ksadk/plugins/teams/cloud_contracts.py +928 -0
- ksadk/plugins/teams/cloud_permits.py +250 -0
- ksadk/plugins/teams/context.py +96 -0
- ksadk/plugins/teams/contracts.py +136 -0
- ksadk/plugins/teams/domain.py +1480 -0
- ksadk/plugins/teams/domain_values.py +24 -0
- ksadk/plugins/teams/effect_contracts.py +255 -0
- ksadk/plugins/teams/errors.py +13 -0
- ksadk/plugins/teams/execution_projection.py +255 -0
- ksadk/plugins/teams/leader_decisions.py +106 -0
- ksadk/plugins/teams/migration.py +311 -0
- ksadk/plugins/teams/migration_cli.py +322 -0
- ksadk/plugins/teams/postgres_store.py +352 -0
- ksadk/plugins/teams/release_contracts.py +60 -0
- ksadk/plugins/teams/runtime.py +853 -0
- ksadk/plugins/teams/source_projection.py +84 -0
- ksadk/plugins/teams/store.py +519 -0
- ksadk/plugins/teams/task_decisions.py +436 -0
- ksadk/plugins/teams/tool_service.py +152 -0
- ksadk/plugins/teams/transport.py +96 -0
- ksadk/plugins/teams/workspaces.py +255 -0
- ksadk/resource_runtime/skill_runtime.py +3 -0
- ksadk/resource_runtime/socket_server.py +11 -0
- ksadk/resource_runtime/worker.py +28 -4
- ksadk/runners/_adk_a2a_stream.py +122 -0
- ksadk/runners/_adk_canonical_stream.py +474 -0
- ksadk/runners/_langgraph_canonical_streams.py +527 -0
- ksadk/runners/_langgraph_runner_streams.py +83 -401
- ksadk/runners/_langgraph_state_reader.py +106 -0
- ksadk/runners/_langgraph_tool_projection.py +86 -0
- ksadk/runners/_langgraph_tools_transformer.py +27 -0
- ksadk/runners/_session_identity.py +285 -0
- ksadk/runners/adk_runner.py +220 -350
- ksadk/runners/base_runner.py +6 -5
- ksadk/runners/langgraph_runner.py +208 -195
- ksadk/runners/patch_langchain.py +47 -0
- ksadk/runners/usage_accumulator.py +11 -3
- ksadk/runners/utils/loader.py +28 -14
- ksadk/runtime/_runner_adapter/a2ui_surface.py +145 -0
- ksadk/runtime/_runner_adapter/gateway_approvals.py +93 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +134 -129
- ksadk/runtime/adapter.py +40 -3
- ksadk/runtime/conversation_execution.py +29 -3
- ksadk/runtime/executor.py +105 -7
- ksadk/runtime/factory.py +45 -3
- ksadk/runtime/framework_adapters.py +184 -10
- ksadk/runtime/hosted_finalizer.py +4 -6
- ksadk/runtime/launch.py +2 -0
- ksadk/runtime/preprocessing.py +11 -3
- ksadk/runtime/runner_adapter.py +160 -21
- ksadk/runtime/skill_eval_result.py +36 -0
- ksadk/runtime/timing.py +46 -0
- ksadk/runtime/usage.py +49 -18
- ksadk/runtime_context.py +105 -1
- ksadk/sandbox/backends/e2b.py +272 -24
- ksadk/sandbox/backends/local_process.py +73 -39
- ksadk/sandbox/base.py +62 -1
- ksadk/sandbox/diagnostics.py +82 -0
- ksadk/sandbox/e2b_connection.py +9 -1
- ksadk/sandbox/local_controls.py +776 -0
- ksadk/sandbox/local_launcher.py +124 -0
- ksadk/scheduler/dispatcher.py +1 -1
- ksadk/server/factory.py +178 -25
- ksadk/server/history_deadline.py +158 -0
- ksadk/server/history_page_cache.py +129 -0
- ksadk/server/invocation_identity.py +118 -0
- ksadk/server/routes/checkpoint_audit.py +88 -0
- ksadk/server/routes/checkpoint_listing.py +294 -0
- ksadk/server/routes/checkpoint_resolution.py +73 -20
- ksadk/server/routes/common.py +32 -3
- ksadk/server/routes/control.py +112 -9
- ksadk/server/routes/dependencies.py +2 -1
- ksadk/server/routes/display_projection.py +154 -0
- ksadk/server/routes/kernel_ingress.py +26 -3
- ksadk/server/routes/misc.py +331 -33
- ksadk/server/routes/models.py +28 -4
- ksadk/server/routes/openai_compat.py +82 -13
- ksadk/server/routes/projection.py +159 -216
- ksadk/server/routes/run.py +112 -37
- ksadk/server/routes/sessions.py +493 -358
- ksadk/server/routes/streaming.py +68 -15
- ksadk/server/routes/tool_receipt_claim_routes.py +107 -0
- ksadk/server/routes/workspace.py +135 -23
- ksadk/server/static/assets/{ArtifactsPanel-CGbWlMNh.js → ArtifactsPanel-CILaW-fC.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-b4C7upmO.js → CodeBlock-BVrNVtJC.js} +10 -7
- ksadk/server/static/assets/MathMessageMarkdown-Dp7c4wWD.js +8 -0
- ksadk/server/static/assets/{MermaidBlock-BEBN4Ksz.js → MermaidBlock-Dxop8Rd7.js} +9 -9
- ksadk/server/static/assets/{NativeTerminalPanel-zvL-WpAn.js → NativeTerminalPanel-Cjjz8B30.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-BYafW7Za.js → abnfDiagram-VCTEODGH-ckWDmOzw.js} +1 -1
- ksadk/server/static/assets/{arc-bTohInsS.js → arc-DqxJede-.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-pn_DuLy6.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-Ch5Lbzsi.js → architectureDiagram-5GKGNRK7-SEQA4bVi.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-kqF4MDOG.js → blockDiagram-NRAW4CY4-pM0B-6NJ.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-8uSRIODv.js → c4Diagram-UCG6FXSJ-m2tuCNVq.js} +1 -1
- ksadk/server/static/assets/channel-fySsnkft.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-BheGxKRQ.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-DkhehZDr.js → chunk-5VM5RSS4-GJHDr9Ww.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-BdXhtPe9.js → chunk-F27PBJKO-CbAN45AV.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-DPTpvpwi.js → chunk-G27WJ6UU-D5ngbbzF.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-Pqoh38el.js → chunk-JWPE2WC7-Cc5IwuYC.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-DeU00qC-.js → chunk-LCL6LL3I-CprgDDdJ.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-CzxScFkr.js → chunk-POPQ4Y6H-CDZhSy82.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-DpqMx-ux.js → chunk-RHFEMEQ7-CZchUNro.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-C6t0SypQ.js → chunk-SVP7TREG-BVruqk6T.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-DVaeafZk.js → chunk-XXDRQBXY-4ld9SxPN.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DSfLtNYm.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DSfLtNYm.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-Bf9D3tzJ.js → cose-bilkent-JH36ORCC-Dmyyk_Vp.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-PFWIGtvA.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-B9cY3iTz.js → cynefinDiagram-5FMLGOSQ-BIyq3EJb.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-CSwlx3CB.js → dagre-3AP2YEHR-eBw08Qdk.js} +1 -1
- ksadk/server/static/assets/{dagre-UBpgon5q.js → dagre-DfvnEf45.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-C9akhN9a.js → diagram-S7CK7UJ4-CUSrE3Mg.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-CgedhTPI.js → diagram-UQ7AKVKN-Dq5Zw2gy.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-Cjucz5BY.js → diagram-VSXAHHWV-DyUHfNwT.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-Dd4Vleyn.js → diagram-VX7I27RA-CREmM89O.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-CZ2jGtYW.js → diagram-Z3DM3KII-Dw9zhHUK.js} +1 -1
- ksadk/server/static/assets/{dist-Bj730Ib4.js → dist-0uJ4tdx6.js} +1 -1
- ksadk/server/static/assets/{dist-BnaTiyzN.js → dist-AEv-vXf-.js} +1 -1
- ksadk/server/static/assets/{dist-BgV_owcB.js → dist-B49GzZDb.js} +1 -1
- ksadk/server/static/assets/{dist-D_ctSS2i.js → dist-B8Myiet9.js} +1 -1
- ksadk/server/static/assets/{dist-ufY3F5p6.js → dist-BBsYarrI.js} +2 -2
- ksadk/server/static/assets/{dist-ClYUxfeK.js → dist-BQMrUwdI.js} +1 -1
- ksadk/server/static/assets/{dist-CFzsn6BG.js → dist-BX_-FENW.js} +1 -1
- ksadk/server/static/assets/{dist-B3FR1SKK.js → dist-CD4L6ymH.js} +1 -1
- ksadk/server/static/assets/{dist-Cf125P_m.js → dist-CHpW1BHj.js} +1 -1
- ksadk/server/static/assets/{dist-DVCOWtha.js → dist-CVPau3tn.js} +1 -1
- ksadk/server/static/assets/{dist-DFD6Qa6T.js → dist-C_qdxcSH.js} +1 -1
- ksadk/server/static/assets/{dist-BIr4DSRq.js → dist-Co68Apq4.js} +1 -1
- ksadk/server/static/assets/{dist-DiixqL0J.js → dist-Cp2PaOMY.js} +1 -1
- ksadk/server/static/assets/{dist-fBO2v5Kt.js → dist-CuR5Bgme.js} +1 -1
- ksadk/server/static/assets/{dist-DoVSwoDb.js → dist-DDSyLEOX.js} +1 -1
- ksadk/server/static/assets/{dist-C5_ZAZsG.js → dist-DUxO0R99.js} +1 -1
- ksadk/server/static/assets/{dist-C1NMAY5a.js → dist-QzbGrED_.js} +1 -1
- ksadk/server/static/assets/dist-bupkyEBZ.js +1 -0
- ksadk/server/static/assets/{dist-DvFK7p5S.js → dist-cxTGfdqx.js} +1 -1
- ksadk/server/static/assets/{dist-CB9iJrvg.js → dist-hL5p1til.js} +1 -1
- ksadk/server/static/assets/{dist-btbmmX5O.js → dist-lOy23y43.js} +1 -1
- ksadk/server/static/assets/{dist-QfkEHOlb.js → dist-pq-nD33R.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Ci8NqqZu.js → ebnfDiagram-PWID7BFC-BS5LdNoR.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-CCIOhLZG.js → erDiagram-SSCWMZ5O-Bv1QtiY-.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-DsM4upQX.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-D5NA16k9.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-Cp_r9Hb6.js → ganttDiagram-EL5Y4UJY-musi-e0G.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-DUCVz-ih.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-Dlhc8Wr8.js → gitGraphDiagram-WWUBYQGX-DH37EGkG.js} +1 -1
- ksadk/server/static/assets/index-CZdkAvsv.js +244 -0
- ksadk/server/static/assets/index-CvXdHgF3.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-rw0ZNHZp.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-CO0rRTRA.js → infoDiagram-RXCK75RN-C1kEtv2y.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-CHiaedSS.js → ishikawaDiagram-5VMMS53U-BCNPyMka.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-CHcCIB09.js → journeyDiagram-EYS64GPL--cDbKnjP.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-CJ_ebBZf.js → kanban-definition-3QL26DDD-R1rAVw2f.js} +1 -1
- ksadk/server/static/assets/{katex-B4weu383.js → katex-BT-X-V7U.js} +1 -1
- ksadk/server/static/assets/{linear-V-MgYHW1.js → linear-DTR631iG.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-CBijEX_c.js → mermaid-parser.core-DfFzJhX8.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-Bc-pQvpp.js → mindmap-definition-FBJOCRG2-D4by_rmk.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-Ca4Hvki9.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-D7genGLl.js → pegDiagram-XKGWAZYB-xWXPEh0c.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-hwiz6fbw.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-Bxj32zwD.js → pieDiagram-E7YTZNPT-BudBDlCI.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BURv2mGh.js → quadrantDiagram-AXDQQJYC-DXZ0PCJF.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-BR1oZXLM.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-DfgEISfX.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-CWOC3rdk.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CiRvHCLY.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-DSDDyJ2f.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-CCsh3avW.js → railroadDiagram-O6MQD6OU-BdAYRYB8.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-_FRRBKEd.js → requirementDiagram-EFPCY7ZU-CBBmaL70.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-C-gr2o9M.js → sankeyDiagram-P5KCCOFB-Doc3rdwp.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-D97NmfKg.js → sequenceDiagram-WJ2MYXX4-0J8R8hWW.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-DjrPC5FX.js → sizeCapture-X5ZJPWSS-CfFLlWsK.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DzMNg1hh.js → stateDiagram-HBIQ2CUA-jBwkjtTy.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-Cw1fPb5w.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-Cssf_CsU.js → swimlanes-XN3QIQJK-B0WxTQf8.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-Cc3JMBAF.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-BWTHzyS1.js → timeline-definition-24CTP7MA-BfCT_75A.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-C05SJHxo.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-BqNLAhc3.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-BSKgJsmR.js → vennDiagram-4TSXK5OY-9gX1VMDC.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-DokNktqB.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-eflHNoaZ.js → wardleyDiagram-VM6X3IG4-sEs_BP6O.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-eYqrQ-v4.js → xychartDiagram-S5SC5T6Z-Ll4jvUN9.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/server/terminal_sessions.py +34 -2
- ksadk/sessions/__init__.py +8 -1
- ksadk/sessions/_checkpoint_resume_audit.py +44 -0
- ksadk/sessions/_in_memory_query.py +112 -0
- ksadk/sessions/_local_service_sync.py +157 -156
- ksadk/sessions/_local_state_sync.py +152 -0
- ksadk/sessions/_local_tables.py +2 -0
- ksadk/sessions/_postgres_checkpoint_read.py +323 -0
- ksadk/sessions/_postgres_checkpoint_stats.py +330 -0
- ksadk/sessions/_postgres_history_read.py +678 -0
- ksadk/sessions/_postgres_schema.py +44 -2
- ksadk/sessions/_postgres_tables.py +2 -0
- ksadk/sessions/_postgres_tool_receipts.py +169 -0
- ksadk/sessions/_resilient_read.py +630 -0
- ksadk/sessions/_tool_receipt_sync.py +119 -0
- ksadk/sessions/agent_identity.py +16 -0
- ksadk/sessions/base.py +278 -0
- ksadk/sessions/db_budget.py +332 -0
- ksadk/sessions/errors.py +12 -0
- ksadk/sessions/history_budget.py +36 -0
- ksadk/sessions/history_identity.py +27 -0
- ksadk/sessions/in_memory.py +376 -113
- ksadk/sessions/invocation_identity.py +235 -0
- ksadk/sessions/local_service.py +345 -0
- ksadk/sessions/postgres_service.py +515 -549
- ksadk/sessions/resilience.py +21 -14
- ksadk/sessions/resilient.py +611 -504
- ksadk/skills/__init__.py +26 -0
- ksadk/skills/events.py +464 -0
- ksadk/skills/loader.py +44 -2
- ksadk/skills/manifest_cache.py +263 -0
- ksadk/skills/mcp_server/__init__.py +0 -0
- ksadk/skills/mcp_server/_registry.py +33 -0
- ksadk/skills/mcp_server/_routing.py +26 -0
- ksadk/skills/mcp_server/_workspace_files.py +160 -0
- ksadk/skills/mcp_server/register.py +415 -0
- ksadk/skills/mcp_server/server.py +218 -0
- ksadk/skills/models.py +11 -0
- ksadk/skills/observability.py +132 -0
- ksadk/skills/package_store.py +75 -2
- ksadk/skills/runtime/agent.py +38 -5
- ksadk/skills/runtime/artifacts.py +87 -0
- ksadk/skills/runtime/backends/e2b.py +400 -56
- ksadk/skills/runtime/backends/local.py +621 -119
- ksadk/skills/runtime/base.py +78 -7
- ksadk/skills/runtime/executor.py +266 -1
- ksadk/skills/runtime/loader.py +108 -3
- ksadk/skills/runtime/request.py +18 -0
- ksadk/skills/service_client.py +6 -1
- ksadk/skills/tool_defs.py +108 -3
- ksadk/studio/a2a_bindings.py +407 -0
- ksadk/studio/a2a_orchestration.py +178 -0
- ksadk/studio/active_run_output.py +92 -0
- ksadk/studio/activity_records.py +542 -0
- ksadk/studio/agent_configuration.py +173 -0
- ksadk/studio/api.py +784 -419
- ksadk/studio/api_a2a_routes.py +93 -0
- ksadk/studio/api_catalog_routes.py +13 -0
- ksadk/studio/api_cloud_chat_routes.py +382 -0
- ksadk/studio/api_contracts.py +31 -2
- ksadk/studio/api_plugin_routes.py +39 -12
- ksadk/studio/api_resource_connections.py +79 -41
- ksadk/studio/attachment_store.py +14 -2
- ksadk/studio/authoring.py +12 -6
- ksadk/studio/authoring_coordinator.py +4 -2
- ksadk/studio/build_resolution.py +117 -0
- ksadk/studio/build_singleflight.py +37 -0
- ksadk/studio/builder.py +32 -2
- ksadk/studio/capabilities.py +31 -9
- ksadk/studio/channel_connections.py +333 -0
- ksadk/studio/cloud.py +113 -387
- ksadk/studio/cloud_gateways.py +424 -0
- ksadk/studio/cloud_shared_web.py +82 -2
- ksadk/studio/codex_agent_service.py +19 -2
- ksadk/studio/codex_builder.py +1 -1
- ksadk/studio/codex_manifest.py +5 -1
- ksadk/studio/codex_provider_build.py +65 -0
- ksadk/studio/codex_run.py +6 -1
- ksadk/studio/compiler.py +112 -6
- ksadk/studio/configuration.py +182 -0
- ksadk/studio/contract_base.py +19 -0
- ksadk/studio/contracts.py +260 -202
- ksadk/studio/dsh_application.py +183 -27
- ksadk/studio/dsh_capability_service.py +302 -189
- ksadk/studio/dsh_lifecycle.py +58 -0
- ksadk/studio/dsh_mcp_transport.py +191 -0
- ksadk/studio/dsh_provider_registration.py +55 -16
- ksadk/studio/entry.py +53 -0
- ksadk/studio/event_store.py +53 -5
- ksadk/studio/execution_host.py +598 -0
- ksadk/studio/framework_run.py +50 -10
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/interaction_submission.py +265 -0
- ksadk/studio/kernel_registry.py +429 -0
- ksadk/studio/kernel_sqlite_migration.py +181 -0
- ksadk/studio/lifecycle_status.py +43 -0
- ksadk/studio/model_buffered_wire.py +227 -0
- ksadk/studio/model_chat_tools.py +143 -0
- ksadk/studio/model_client.py +1939 -345
- ksadk/studio/model_content_stream.py +129 -0
- ksadk/studio/model_credentials.py +196 -0
- ksadk/studio/model_profile_service.py +89 -40
- ksadk/studio/model_request_projection.py +284 -0
- ksadk/studio/model_responses_content.py +296 -0
- ksadk/studio/model_usage.py +109 -0
- ksadk/studio/model_wire_failure.py +110 -0
- ksadk/studio/model_wire_identity.py +138 -0
- ksadk/studio/model_wire_snapshot.py +45 -0
- ksadk/studio/model_wire_tools.py +283 -0
- ksadk/studio/native_acceptance.py +68 -0
- ksadk/studio/native_approval.py +111 -0
- ksadk/studio/native_browser_port.py +861 -0
- ksadk/studio/native_child_mcp.py +87 -0
- ksadk/studio/native_child_publication.py +68 -0
- ksadk/studio/native_child_skills.py +72 -0
- ksadk/studio/native_child_workspace.py +176 -0
- ksadk/studio/native_command_policy.py +53 -0
- ksadk/studio/native_context_port.py +326 -0
- ksadk/studio/native_context_registration.py +54 -0
- ksadk/studio/native_document_input.py +88 -0
- ksadk/studio/native_documents.py +172 -0
- ksadk/studio/native_final_shape.py +852 -0
- ksadk/studio/native_gateway_stream.py +69 -0
- ksadk/studio/native_hook_port.py +145 -0
- ksadk/studio/native_hook_registration.py +70 -0
- ksadk/studio/native_inventory_disclosure.py +223 -0
- ksadk/studio/native_kernel_admission.py +84 -0
- ksadk/studio/native_language_diagnostics.py +320 -0
- ksadk/studio/native_mcp_disclosure.py +198 -0
- ksadk/studio/native_mcp_tool_port.py +219 -0
- ksadk/studio/native_memory_acceptance.py +362 -0
- ksadk/studio/native_memory_context.py +266 -0
- ksadk/studio/native_memory_flush.py +174 -0
- ksadk/studio/native_memory_sources.py +80 -0
- ksadk/studio/native_memory_tool_port.py +360 -0
- ksadk/studio/native_memory_turn.py +183 -0
- ksadk/studio/native_model_port.py +765 -0
- ksadk/studio/native_model_recovery.py +126 -0
- ksadk/studio/native_options.py +75 -0
- ksadk/studio/native_output_fields.py +58 -0
- ksadk/studio/native_planning.py +156 -0
- ksadk/studio/native_product.py +988 -0
- ksadk/studio/native_read_budget.py +426 -0
- ksadk/studio/native_recovery.py +171 -0
- ksadk/studio/native_scope.py +35 -0
- ksadk/studio/native_session_context_port.py +141 -0
- ksadk/studio/native_session_engine.py +14 -0
- ksadk/studio/native_skill_source.py +74 -0
- ksadk/studio/native_subagent_port.py +700 -0
- ksadk/studio/native_terminal_recovery.py +95 -0
- ksadk/studio/native_test_action.py +610 -0
- ksadk/studio/native_tool_port.py +477 -0
- ksadk/studio/native_user_input.py +60 -0
- ksadk/studio/native_verification_presentation.py +65 -0
- ksadk/studio/native_workspace_diagnostics.py +183 -0
- ksadk/studio/native_workspace_port.py +43 -0
- ksadk/studio/operations.py +34 -9
- ksadk/studio/plugin_composition.py +39 -12
- ksadk/studio/plugin_kernel_adapter.py +89 -0
- ksadk/studio/plugin_lifecycle.py +80 -0
- ksadk/studio/plugin_native_activation.py +163 -0
- ksadk/studio/plugin_runtime.py +832 -99
- ksadk/studio/plugin_runtime_admission.py +57 -0
- ksadk/studio/presentation.py +92 -0
- ksadk/studio/provider_recovery.py +59 -0
- ksadk/studio/public_output_store.py +69 -0
- ksadk/studio/recovery_page.py +70 -0
- ksadk/studio/resource_authority.py +69 -34
- ksadk/studio/resource_catalog.py +14 -12
- ksadk/studio/run_activity.py +848 -0
- ksadk/studio/run_controls.py +185 -0
- ksadk/studio/run_documents.py +127 -0
- ksadk/studio/run_service.py +1220 -335
- ksadk/studio/run_submission_receipts.py +68 -0
- ksadk/studio/runtime_catalog.py +73 -11
- ksadk/studio/runtime_prewarm.py +22 -0
- ksadk/studio/runtime_source.py +1 -1
- ksadk/studio/scheduler_assistant.py +4 -1
- ksadk/studio/scheduler_runtime.py +32 -289
- ksadk/studio/service.py +615 -246
- ksadk/studio/service_settings.py +229 -0
- ksadk/studio/shared_web.py +518 -181
- ksadk/studio/shared_web_response_events.py +180 -0
- ksadk/studio/shared_web_run_state.py +133 -0
- ksadk/studio/skill_discovery.py +305 -3
- ksadk/studio/static/assets/ChannelsPage-xX5HlMId.js +8 -0
- ksadk/studio/static/assets/CodeBlock-DMIf4h33-CkufcPYo.js +44 -0
- ksadk/studio/static/assets/ExecutionTree-v3HexvPo-Bk8wzGSy.js +1 -0
- ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-DYG1Wzbx.js +8 -0
- ksadk/studio/static/assets/MermaidBlock-H_0qzQDf-BdXtxug_.js +11 -0
- ksadk/studio/static/assets/MessageMarkdown-CwMTvgFj-BeyLyQHu.js +59 -0
- ksadk/studio/static/assets/PrismRenderer-5pK6tUdb.js +3 -0
- ksadk/studio/static/assets/TeamsPage-CCaaGaVp.js +3 -0
- ksadk/studio/static/assets/abnfDiagram-VCTEODGH-Bnb1O3tA.js +1 -0
- ksadk/studio/static/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
- ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-5Zn1gqJv.js +36 -0
- ksadk/studio/static/assets/{blockDiagram-I7D4REHJ-CFmPl1de.js → blockDiagram-I7D4REHJ-CLB4L11u.js} +6 -6
- ksadk/studio/static/assets/{c4Diagram-7LVT6UL2-BRrGu8qt.js → c4Diagram-7LVT6UL2-BOma_Z_F.js} +7 -7
- ksadk/studio/static/assets/channel-r8itPEik.js +1 -0
- ksadk/studio/static/assets/chunk-2E4U76K2-BFf_huql.js +2 -0
- ksadk/studio/static/assets/chunk-2ZTRR5NV-C2qZJila.js +2 -0
- ksadk/studio/static/assets/chunk-3Z5EZCMW-DMniOlPd.js +1 -0
- ksadk/studio/static/assets/{chunk-4HAMMTFA-D220vUtq.js → chunk-4HAMMTFA-DVtDP4rm.js} +7 -7
- ksadk/studio/static/assets/chunk-5V3GS4D5-i-ZDOEnP.js +1 -0
- ksadk/studio/static/assets/chunk-6AZGARVD-BB6FqMl-.js +1 -0
- ksadk/studio/static/assets/chunk-6EIED4P4-DdUJkP0f.js +1 -0
- ksadk/studio/static/assets/chunk-6K3QC6MW-CqVsVn-y.js +1 -0
- ksadk/studio/static/assets/chunk-6TQVIW2G-DKlLbNIP.js +1 -0
- ksadk/studio/static/assets/chunk-747NJXEK-BFkZPlab.js +2 -0
- ksadk/studio/static/assets/chunk-75Z2AOVW-BZNhM4vB.js +2 -0
- ksadk/studio/static/assets/chunk-CLGD4ZFX-D-GTtyKs.js +1 -0
- ksadk/studio/static/assets/{chunk-DU6HZSFF-C4DtVpCu.js → chunk-DU6HZSFF-CpEZCUgg.js} +1 -1
- ksadk/studio/static/assets/chunk-F27PBJKO-D7pvKf0C.js +1 -0
- ksadk/studio/static/assets/{chunk-GMAD6QVW-DiQA-u2N.js → chunk-GMAD6QVW-DE_8H717.js} +6 -6
- ksadk/studio/static/assets/chunk-GVQU2GXP-BfEB0lNv.js +1 -0
- ksadk/studio/static/assets/chunk-I5DQTOEV-Hm2LfiYI.js +1 -0
- ksadk/studio/static/assets/chunk-ICYGCRZG-749o-pGJ.js +1 -0
- ksadk/studio/static/assets/chunk-IH6LHLGP-CC6I9F3i.js +1 -0
- ksadk/studio/static/assets/{chunk-IMKFNOWR-BSlbjT_b.js → chunk-IMKFNOWR-D38axYOR.js} +5 -5
- ksadk/studio/static/assets/chunk-KI3K4JFJ-Rma-0k_M.js +1 -0
- ksadk/studio/static/assets/chunk-L3NEJ4N5-Dq5xn7aL.js +1 -0
- ksadk/studio/static/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
- ksadk/studio/static/assets/{chunk-OSK3NFVY-M2m6G3j4.js → chunk-OSK3NFVY-DiCQjx9b.js} +5 -5
- ksadk/studio/static/assets/chunk-OUJLGHUK-D0j2JRSg.js +2 -0
- ksadk/studio/static/assets/{chunk-P2QGCYS3-DezaQxiK.js → chunk-P2QGCYS3-VdhKmfYq.js} +1 -1
- ksadk/studio/static/assets/chunk-POPQ4Y6H-C1_rzO5M.js +1 -0
- ksadk/studio/static/assets/chunk-PWAF6VOD-B45HvflI.js +1 -0
- ksadk/studio/static/assets/{chunk-SHT3W25Y-DAyIVp5H.js → chunk-SHT3W25Y-DsCFnRVE.js} +9 -9
- ksadk/studio/static/assets/{chunk-SVP7TREG-DulomOrS.js → chunk-SVP7TREG-DljApgRX.js} +1 -1
- ksadk/studio/static/assets/{chunk-TICWLB2K-bHJYoflj.js → chunk-TICWLB2K-DWqRtyqa.js} +6 -6
- ksadk/studio/static/assets/chunk-TLUHSLCS-DUeGgnD5.js +2 -0
- ksadk/studio/static/assets/chunk-UY3FDG6J-CnmaAkG5.js +1 -0
- ksadk/studio/static/assets/chunk-XHIXRSVI-BFQ6B8BE.js +1 -0
- ksadk/studio/static/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
- ksadk/studio/static/assets/classDiagram-ZZMXUADV-DjeMNJmu.js +1 -0
- ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-DjeMNJmu.js +1 -0
- ksadk/studio/static/assets/clsx-DB0hHKMi.js +1 -0
- ksadk/studio/static/assets/cose-bilkent-JH36ORCC-om8AY0Mc.js +1 -0
- ksadk/studio/static/assets/createLucideIcon-DR0LGTP5.js +1 -0
- ksadk/studio/static/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
- ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-6BrmK52S.js +62 -0
- ksadk/studio/static/assets/dagre-GXQ25YYZ-buZBRlwz.js +4 -0
- ksadk/studio/static/assets/{diagram-S7CK7UJ4-0BZ98mya.js → diagram-S7CK7UJ4-C6aH78c4.js} +3 -3
- ksadk/studio/static/assets/diagram-UQ7AKVKN-DY_tQ6C_.js +41 -0
- ksadk/studio/static/assets/diagram-VSXAHHWV-DX9p5cQT.js +3 -0
- ksadk/studio/static/assets/diagram-VX7I27RA-DB2WSH8X.js +24 -0
- ksadk/studio/static/assets/diagram-Z3DM3KII-Ce1BJwHE.js +24 -0
- ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-Daukxwqf.js +1 -0
- ksadk/studio/static/assets/{erDiagram-RLTQ6QDP-DLGry2Ap.js → erDiagram-RLTQ6QDP-CGthTMmJ.js} +6 -6
- ksadk/studio/static/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
- ksadk/studio/static/assets/flowDiagram-HODETNUW-CZ1tEenp.js +1 -0
- ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BfoAZW9X.js +292 -0
- ksadk/studio/static/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
- ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-S985OIJF.js +106 -0
- ksadk/studio/static/assets/iconfont-4HN3C9Pg.woff2 +0 -0
- ksadk/studio/static/assets/index-C7DNDik2.css +2 -0
- ksadk/studio/static/assets/index-Cy68gTlH.js +265 -0
- ksadk/studio/static/assets/index.dom-BQenBtFm.js +1 -0
- ksadk/studio/static/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
- ksadk/studio/static/assets/infoDiagram-27XIBGKW-CO3W8tFn.js +2 -0
- ksadk/studio/static/assets/{ishikawaDiagram-5VMMS53U-CljI2wyf.js → ishikawaDiagram-5VMMS53U-BUGEl8cO.js} +2 -2
- ksadk/studio/static/assets/{journeyDiagram-3NMN7TZE-D50Fo2sL.js → journeyDiagram-3NMN7TZE-Ch2ZYxvr.js} +6 -6
- ksadk/studio/static/assets/{kanban-definition-UXKFOSKX-DimfOTam.js → kanban-definition-UXKFOSKX-Da-Fp4vC.js} +3 -3
- ksadk/studio/static/assets/kingsoft-cloud-BOcLiQ3e.ico +0 -0
- ksadk/studio/static/assets/{line-CX5BOOUP.js → line-BDaeq300.js} +1 -1
- ksadk/studio/static/assets/linear-DmucvK78.js +1 -0
- ksadk/studio/static/assets/mermaid-parser.core-Cj5mYdBi.js +4 -0
- ksadk/studio/static/assets/{mindmap-definition-YA3MSWOX-Bizzqy5R.js → mindmap-definition-YA3MSWOX-CvlsVQ38.js} +1 -1
- ksadk/studio/static/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
- ksadk/studio/static/assets/pegDiagram-XKGWAZYB-BuHuNgT5.js +1 -0
- ksadk/studio/static/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
- ksadk/studio/static/assets/{pieDiagram-E7YTZNPT-B9mrFSDF.js → pieDiagram-E7YTZNPT-DlrWiSOv.js} +1 -1
- ksadk/studio/static/assets/preload-helper-CO68xz6u.js +1 -0
- ksadk/studio/static/assets/presentation-yYw7xK60-BmcZriri.js +1 -0
- ksadk/studio/static/assets/{quadrantDiagram-AXDQQJYC-NzUfCbMR.js → quadrantDiagram-AXDQQJYC-BA3hpUZM.js} +6 -6
- ksadk/studio/static/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
- ksadk/studio/static/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
- ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
- ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
- ksadk/studio/static/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
- ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-BxpAazym.js +1 -0
- ksadk/studio/static/assets/{requirementDiagram-BXWQKSXE-BF3d103U.js → requirementDiagram-BXWQKSXE-E-Ge0FcV.js} +1 -1
- ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-CA6YKhQ3.js +40 -0
- ksadk/studio/static/assets/{sequenceDiagram-WJ2MYXX4-Dh4wZtUm.js → sequenceDiagram-WJ2MYXX4-CcElXGg4.js} +9 -9
- ksadk/studio/static/assets/src-B6xuSHsQ.js +1 -0
- ksadk/studio/static/assets/stateDiagram-D77RDMKH-DdXZOFqY.js +1 -0
- ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-CJbOM-QK.js +1 -0
- ksadk/studio/static/assets/swimlanes-42K2YHIH-NtgEEHLY.js +1 -0
- ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-D-AVlW8B.js +8 -0
- ksadk/studio/static/assets/{timeline-definition-24CTP7MA-DxmSSjRB.js → timeline-definition-24CTP7MA-CIuYhNy3.js} +7 -7
- ksadk/studio/static/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
- ksadk/studio/static/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
- ksadk/studio/static/assets/vennDiagram-4TSXK5OY-7HbApbrv.js +34 -0
- ksadk/studio/static/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
- ksadk/studio/static/assets/{wardleyDiagram-VM6X3IG4-Ba1AOWoe.js → wardleyDiagram-VM6X3IG4-CMJiaedy.js} +1 -1
- ksadk/studio/static/assets/{xychartDiagram-S5SC5T6Z-Cr5pt5Y-.js → xychartDiagram-S5SC5T6Z-BdgtMvW2.js} +6 -6
- ksadk/studio/static/index.html +8 -2
- ksadk/studio/teams_catalog.py +166 -0
- ksadk/studio/teams_installation.py +217 -0
- ksadk/studio/teams_node.py +549 -0
- ksadk/studio/teams_node_factory.py +558 -0
- ksadk/studio/teams_node_runner.py +72 -0
- ksadk/studio/teams_node_v1.py +935 -0
- ksadk/studio/teams_remote.py +496 -0
- ksadk/studio/templates.py +41 -19
- ksadk/studio/validator.py +13 -0
- ksadk/studio/workspace.py +19 -4
- ksadk/studio/workspace_plugins.py +138 -0
- ksadk/studio/workspace_registry.py +286 -0
- ksadk/tools/a2a_policy.py +5 -0
- ksadk/tools/artifact_observation.py +53 -0
- ksadk/tools/captured_source_artifact.py +68 -0
- ksadk/tools/schema.py +34 -0
- ksadk/toolsets/__init__.py +93 -88
- ksadk/toolsets/a2a.py +89 -89
- ksadk/toolsets/sandbox.py +377 -6
- ksadk/toolsets/skill_management.py +315 -0
- ksadk/toolsets/skills.py +446 -378
- ksadk/toolsets/tool_selection.py +94 -0
- ksadk/toolsets/workspace.py +344 -326
- ksadk/toolsets/workspace_editing.py +291 -0
- ksadk/toolsets/workspace_identity.py +15 -0
- ksadk/toolsets/workspace_model_observation.py +104 -0
- ksadk/toolsets/workspace_mutation.py +75 -0
- ksadk/toolsets/workspace_read_budget.py +216 -0
- ksadk/toolsets/workspace_search.py +192 -0
- ksadk/toolsets/workspace_search_process.py +129 -0
- ksadk/toolsets/workspace_state.py +6 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/http_headers.py +18 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +56 -2
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -148
- ksadk_runtime_common/workspace_files/router.py +28 -14
- agentengine_sdk_python-0.8.4.dist-info/RECORD +0 -1070
- ksadk/server/static/assets/MathMessageMarkdown-DbdW5A4_.js +0 -8
- ksadk/server/static/assets/architecture-7GRP2DOG-BW5UtSnx.js +0 -1
- ksadk/server/static/assets/channel-CF5Up3hQ.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-KkXX47WI.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Cz81acRQ.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Cz81acRQ.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DVWs8a2t.js +0 -1
- ksadk/server/static/assets/dist-B2_CVgKv.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CQqR2hg3.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-DDRRbGPm.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-fgWEf7UD.js +0 -1
- ksadk/server/static/assets/index-DcTML1Up.css +0 -2
- ksadk/server/static/assets/index-Dh2XHqPK.js +0 -244
- ksadk/server/static/assets/info-A6RAGUB7-BUecLTdt.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BeW-btFv.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-X0xh4SaT.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DzRMSqjd.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-Cc44icX-.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-D6S4BdV-.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-vD3aVE2o.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-CYbW_b4T.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BIcjKFhF.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-tq3K3Ybl.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-DBXt3GdU.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-DrCy4E_l.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CefhOliM.js +0 -1
- ksadk/studio/static/assets/CodeBlock-DCUUY0PL-BLjA7kbS.js +0 -41
- ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--CUYIXwgI.js +0 -8
- ksadk/studio/static/assets/MermaidBlock-DBFTztfQ-BTYKWEbD.js +0 -44
- ksadk/studio/static/assets/PrismRenderer-wiXRHURw.js +0 -3
- ksadk/studio/static/assets/abnfDiagram-VCTEODGH-CLETGjKq.js +0 -1
- ksadk/studio/static/assets/architecture-7GRP2DOG-hXMiO_AU.js +0 -1
- ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-B5EQysk6.js +0 -36
- ksadk/studio/static/assets/channel-BqO-gtEw.js +0 -1
- ksadk/studio/static/assets/chunk-75Z2AOVW-C8lLv_qc.js +0 -2
- ksadk/studio/static/assets/chunk-F27PBJKO-Cpw14SNG.js +0 -1
- ksadk/studio/static/assets/chunk-GVQU2GXP-DUdNBUCi.js +0 -1
- ksadk/studio/static/assets/chunk-L3NEJ4N5-Db8Ab22W.js +0 -1
- ksadk/studio/static/assets/chunk-POPQ4Y6H-DOpufJ3t.js +0 -1
- ksadk/studio/static/assets/chunk-PWAF6VOD-CfLb1gSc.js +0 -1
- ksadk/studio/static/assets/chunk-XXDRQBXY-7PcdR2B1.js +0 -1
- ksadk/studio/static/assets/classDiagram-ZZMXUADV-D_bW8JjA.js +0 -1
- ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-D_bW8JjA.js +0 -1
- ksadk/studio/static/assets/cose-bilkent-JH36ORCC-snL88wkX.js +0 -1
- ksadk/studio/static/assets/cynefin-OW5HDTMX-Bd1YhRo1.js +0 -1
- ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-BBrIG_mI.js +0 -62
- ksadk/studio/static/assets/dagre-GXQ25YYZ-DtF1nGdY.js +0 -4
- ksadk/studio/static/assets/diagram-UQ7AKVKN-6mLKEo6L.js +0 -41
- ksadk/studio/static/assets/diagram-VSXAHHWV-CJBIaiZ2.js +0 -3
- ksadk/studio/static/assets/diagram-VX7I27RA-BohLEE1C.js +0 -24
- ksadk/studio/static/assets/diagram-Z3DM3KII-D8KIGonG.js +0 -24
- ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-C99HU61t.js +0 -1
- ksadk/studio/static/assets/eventmodeling-NTZA5JFV-B1_5Ac8j.js +0 -1
- ksadk/studio/static/assets/flowDiagram-HODETNUW-DD5hmicn.js +0 -1
- ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BPJrVLSG.js +0 -292
- ksadk/studio/static/assets/gitGraph-4MIJSDKK-Cy_ZQBGi.js +0 -1
- ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-CZCUz4MT.js +0 -106
- ksadk/studio/static/assets/index-CeHL9ARV.css +0 -2
- ksadk/studio/static/assets/index-D9QEcVpL.js +0 -320
- ksadk/studio/static/assets/info-A6RAGUB7-DBzDQB_I.js +0 -1
- ksadk/studio/static/assets/infoDiagram-27XIBGKW-B-sq07Hi.js +0 -2
- ksadk/studio/static/assets/linear-BCxN9JLv.js +0 -1
- ksadk/studio/static/assets/mermaid-parser.core-BdCz8fkS.js +0 -7
- ksadk/studio/static/assets/packet-AYTQ26CC-gPln-_kG.js +0 -1
- ksadk/studio/static/assets/pegDiagram-XKGWAZYB-Dy3fB-gW.js +0 -1
- ksadk/studio/static/assets/pie-WAS4IAKB-qZdHMr7B.js +0 -1
- ksadk/studio/static/assets/radar-RG4KPBEZ-BZlnaea9.js +0 -1
- ksadk/studio/static/assets/railroad-74A4TZTK-Ce3__H9c.js +0 -1
- ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-DqQn7EzD.js +0 -1
- ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-B55kCA8F.js +0 -1
- ksadk/studio/static/assets/railroad-peg-WCYAUIDC-DfQnvkHd.js +0 -1
- ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-DnhMjZCk.js +0 -1
- ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-Bo-JTwt0.js +0 -40
- ksadk/studio/static/assets/src-SFn9St9H.js +0 -1
- ksadk/studio/static/assets/stateDiagram-D77RDMKH-DH774G6d.js +0 -1
- ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-DZvVWPBo.js +0 -1
- ksadk/studio/static/assets/swimlanes-42K2YHIH-Bl13OvTX.js +0 -1
- ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-BnFOTALJ.js +0 -8
- ksadk/studio/static/assets/treeView-Q6P3EWNA-D5Nn9Swz.js +0 -1
- ksadk/studio/static/assets/treemap-WGGIJYW6-CgHgnQhE.js +0 -1
- ksadk/studio/static/assets/vennDiagram-4TSXK5OY-B9QEXcJq.js +0 -34
- ksadk/studio/static/assets/wardley-WFR3VGLG-DXrK2RRY.js +0 -1
- {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/top_level.txt +0 -0
- /ksadk/studio/static/assets/{MathMessageMarkdown-CBcDrAw--BEOyPC3w.css → MathMessageMarkdown-BesSeCYl-BEOyPC3w.css} +0 -0
ksadk/sessions/resilient.py
CHANGED
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import weakref
|
|
5
6
|
from collections.abc import AsyncIterator
|
|
6
|
-
from contextvars import ContextVar
|
|
7
|
+
from contextvars import Context, ContextVar
|
|
7
8
|
from typing import Any, Optional, cast
|
|
8
9
|
|
|
9
10
|
from ksadk.sessions.base import (
|
|
@@ -13,22 +14,27 @@ from ksadk.sessions.base import (
|
|
|
13
14
|
SessionEvent,
|
|
14
15
|
SessionEventQuery,
|
|
15
16
|
SessionState,
|
|
17
|
+
ToolReceiptClaim,
|
|
18
|
+
)
|
|
19
|
+
from ksadk.sessions.db_budget import DatabaseOperationDeadline, session_db_budget
|
|
20
|
+
from ksadk.sessions.errors import (
|
|
21
|
+
CheckpointScanRestartRequired,
|
|
22
|
+
HistoryReloadRequired,
|
|
23
|
+
SessionBackendUnavailable,
|
|
16
24
|
)
|
|
17
|
-
from ksadk.sessions.errors import CheckpointScanRestartRequired
|
|
18
25
|
from ksadk.sessions.in_memory import InMemorySessionService
|
|
19
|
-
from ksadk.sessions.resilience import is_session_backend_failure
|
|
26
|
+
from ksadk.sessions.resilience import is_session_backend_failure, is_session_connection_failure
|
|
27
|
+
|
|
28
|
+
from ksadk.sessions._resilient_read import _ResilientReadMixin
|
|
20
29
|
|
|
21
30
|
logger = logging.getLogger(__name__)
|
|
22
31
|
|
|
23
32
|
|
|
24
|
-
class ResilientSessionService(BaseSessionService):
|
|
33
|
+
class ResilientSessionService(_ResilientReadMixin, BaseSessionService):
|
|
25
34
|
"""Keep live agent sessions available when durable persistence is unavailable.
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
write-through sink. After its first failure it stays disabled until a
|
|
30
|
-
background probe confirms the durable backend is reachable again, at which
|
|
31
|
-
point it is re-enabled and an INFO log is emitted.
|
|
36
|
+
Clean sessions read and write through the durable backend. A session with
|
|
37
|
+
confirmed process-local writes stays live-authoritative after recovery.
|
|
32
38
|
"""
|
|
33
39
|
|
|
34
40
|
_probe_interval_seconds: float = 30.0
|
|
@@ -41,10 +47,16 @@ class ResilientSessionService(BaseSessionService):
|
|
|
41
47
|
self.primary = primary
|
|
42
48
|
self.fallback = fallback or InMemorySessionService()
|
|
43
49
|
self._primary_enabled = True
|
|
44
|
-
self.
|
|
50
|
+
self._hydrate_locks: weakref.WeakValueDictionary[str, asyncio.Lock] = weakref.WeakValueDictionary()
|
|
45
51
|
self._primary_session_lock = asyncio.Lock()
|
|
46
52
|
self._primary_session_ids: set[str] = set()
|
|
53
|
+
self._hydrated_session_ids: set[str] = set()
|
|
54
|
+
self._live_generation: dict[str, int] = {}
|
|
47
55
|
self._dirty_session_ids: set[str] = set()
|
|
56
|
+
self._unsafe_live_session_ids: set[str] = set()
|
|
57
|
+
self._dirty_state_keys: set[tuple[str, str, str, str]] = set()
|
|
58
|
+
self._hydrated_state_keys: set[tuple[str, str, str, str]] = set()
|
|
59
|
+
self._deleted_session_ids: set[str] = set()
|
|
48
60
|
self._checkpoint_partition: ContextVar[
|
|
49
61
|
tuple[frozenset[str], frozenset[str]] | None
|
|
50
62
|
] = ContextVar(f"checkpoint_partition_{id(self)}", default=None)
|
|
@@ -54,20 +66,63 @@ class ResilientSessionService(BaseSessionService):
|
|
|
54
66
|
def canonical_event_service(self) -> BaseSessionService:
|
|
55
67
|
"""Return the single durable authority for canonical runtime facts.
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
Canonical event sequences cannot
|
|
58
70
|
fail over to the independently sequenced fallback without splitting one
|
|
59
71
|
logical log, so RuntimeEventStore binds directly to the primary service.
|
|
60
72
|
"""
|
|
61
73
|
|
|
62
74
|
return self.primary
|
|
63
75
|
|
|
76
|
+
async def ensure_canonical_session_header(
|
|
77
|
+
self, agent_id: str, user_id: str, session_id: str
|
|
78
|
+
) -> Session:
|
|
79
|
+
"""Preserve deletion and recovery fencing without hydrating a transcript."""
|
|
80
|
+
if session_id in self._deleted_session_ids:
|
|
81
|
+
raise SessionBackendUnavailable("Session deletion is pending durable recovery")
|
|
82
|
+
if not self._primary_enabled:
|
|
83
|
+
self._clean_read_unavailable()
|
|
84
|
+
ensure = getattr(self.primary, "ensure_session_header", None)
|
|
85
|
+
if callable(ensure):
|
|
86
|
+
header = await ensure(agent_id, user_id, session_id)
|
|
87
|
+
else:
|
|
88
|
+
header = await self.primary.create_session(agent_id, user_id, session_id)
|
|
89
|
+
if session_id in self._deleted_session_ids:
|
|
90
|
+
raise SessionBackendUnavailable("Session deletion is pending durable recovery")
|
|
91
|
+
return header
|
|
92
|
+
|
|
64
93
|
@property
|
|
65
94
|
def degraded(self) -> bool:
|
|
66
95
|
return not self._primary_enabled
|
|
67
96
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
97
|
+
def persistence_degraded(self, session_id: str | None = None) -> bool:
|
|
98
|
+
"""Whether a read may contain process-local facts absent from durable storage."""
|
|
99
|
+
return not self._primary_enabled or (
|
|
100
|
+
bool(self._dirty_session_ids or self._dirty_state_keys or self._deleted_session_ids)
|
|
101
|
+
if session_id is None
|
|
102
|
+
else session_id in self._dirty_session_ids
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _mark_dirty(self, session_id: str | None) -> None:
|
|
106
|
+
if session_id:
|
|
107
|
+
if session_id in self._unsafe_live_session_ids:
|
|
108
|
+
self._clean_read_unavailable()
|
|
109
|
+
self._dirty_session_ids.add(session_id)
|
|
110
|
+
|
|
111
|
+
def _has_complete_live_session(self, session_id: str) -> bool:
|
|
112
|
+
return (
|
|
113
|
+
session_id in self._hydrated_session_ids
|
|
114
|
+
and session_id not in self._unsafe_live_session_ids
|
|
115
|
+
and session_id not in self._deleted_session_ids
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _clean_read_unavailable() -> None:
|
|
120
|
+
raise SessionBackendUnavailable(
|
|
121
|
+
"Durable session history is unavailable; clean history cannot be inferred from a partial live cache"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# The fallback can acquire an independent sequence when persistence fails,
|
|
125
|
+
# so the wrapper itself does not claim canonical storage capabilities.
|
|
71
126
|
|
|
72
127
|
async def _call_primary(self, method_name: str, *args: Any, **kwargs: Any) -> tuple[bool, Any]:
|
|
73
128
|
if not self._primary_enabled:
|
|
@@ -76,8 +131,24 @@ class ResilientSessionService(BaseSessionService):
|
|
|
76
131
|
method = getattr(self.primary, method_name)
|
|
77
132
|
return True, await method(*args, **kwargs)
|
|
78
133
|
except Exception as exc:
|
|
134
|
+
readonly = (getattr(self.primary, "_retry_safe_reads", False)
|
|
135
|
+
and (method_name == "has_events" or
|
|
136
|
+
method_name.startswith(("get_", "list_", "query_", "count_"))))
|
|
137
|
+
if readonly and isinstance(exc, TimeoutError):
|
|
138
|
+
raise # Request/SQL capacity timeout does not change the source.
|
|
139
|
+
if readonly and is_session_connection_failure(exc):
|
|
140
|
+
# A stale idle socket must not disable every clean session for
|
|
141
|
+
# the entire probe interval. Only pure reads may reacquire once;
|
|
142
|
+
# the caller's original deadline still owns the retry.
|
|
143
|
+
try:
|
|
144
|
+
async with asyncio.timeout(2.0):
|
|
145
|
+
return True, await method(*args, **kwargs)
|
|
146
|
+
except TimeoutError:
|
|
147
|
+
raise
|
|
148
|
+
except Exception as retry_error:
|
|
149
|
+
exc = retry_error
|
|
79
150
|
if not is_session_backend_failure(exc):
|
|
80
|
-
raise
|
|
151
|
+
raise exc
|
|
81
152
|
self._disable_primary(exc)
|
|
82
153
|
return False, None
|
|
83
154
|
|
|
@@ -98,7 +169,9 @@ class ResilientSessionService(BaseSessionService):
|
|
|
98
169
|
def _start_probe(self) -> None:
|
|
99
170
|
if self._probe_task is not None and not self._probe_task.done():
|
|
100
171
|
return
|
|
101
|
-
|
|
172
|
+
# The recovery task outlives a request. Do not inherit request byte
|
|
173
|
+
# budgets, checkpoint snapshot connections or identity ContextVars.
|
|
174
|
+
self._probe_task = Context().run(asyncio.create_task, self._probe_loop())
|
|
102
175
|
|
|
103
176
|
async def _probe_loop(self) -> None:
|
|
104
177
|
while not self._primary_enabled:
|
|
@@ -106,7 +179,10 @@ class ResilientSessionService(BaseSessionService):
|
|
|
106
179
|
if self._primary_enabled:
|
|
107
180
|
break
|
|
108
181
|
try:
|
|
109
|
-
|
|
182
|
+
probe = (self.primary.get_session_header if getattr(self.primary, "_retry_safe_reads", False)
|
|
183
|
+
else self.primary.get_session)
|
|
184
|
+
async with asyncio.timeout(2.0):
|
|
185
|
+
await probe("__ksadk_probe__")
|
|
110
186
|
except Exception:
|
|
111
187
|
continue
|
|
112
188
|
self._primary_enabled = True
|
|
@@ -118,21 +194,56 @@ class ResilientSessionService(BaseSessionService):
|
|
|
118
194
|
},
|
|
119
195
|
)
|
|
120
196
|
|
|
121
|
-
async def _hydrate(self, session: Session) -> Session:
|
|
122
|
-
|
|
197
|
+
async def _hydrate(self, session: Session, expected_generation: int) -> Optional[Session]:
|
|
198
|
+
lock = self._hydrate_locks.setdefault(session.id, asyncio.Lock())
|
|
199
|
+
async with lock:
|
|
200
|
+
if session.id in self._deleted_session_ids:
|
|
201
|
+
return None
|
|
202
|
+
if session.id in self._dirty_session_ids:
|
|
203
|
+
live = await self.fallback.get_session(session.id)
|
|
204
|
+
if live is not None:
|
|
205
|
+
return live
|
|
206
|
+
if self._live_generation.get(session.id, 0) != expected_generation:
|
|
207
|
+
# A local mutation completed after the durable snapshot was
|
|
208
|
+
# fetched but before this lock. Refresh under the same lock so
|
|
209
|
+
# an older state snapshot cannot undo that mutation.
|
|
210
|
+
ok, fresh = await self._call_primary("get_session", session.id)
|
|
211
|
+
if not ok:
|
|
212
|
+
if session.id in self._hydrated_session_ids:
|
|
213
|
+
return await self.fallback.get_session(session.id)
|
|
214
|
+
self._clean_read_unavailable()
|
|
215
|
+
if fresh is None:
|
|
216
|
+
return None
|
|
217
|
+
session = cast(Session, fresh)
|
|
123
218
|
self._primary_session_ids.add(session.id)
|
|
124
|
-
existing = await self.fallback.
|
|
219
|
+
existing = await self.fallback.get_session_header(session.id)
|
|
220
|
+
if existing is not None and (
|
|
221
|
+
existing.agent_id != session.agent_id
|
|
222
|
+
or existing.user_id != session.user_id
|
|
223
|
+
or existing.created_at != session.created_at
|
|
224
|
+
):
|
|
225
|
+
# A different process deleted and recreated this ID. Never
|
|
226
|
+
# graft its facts onto the old owner's recovery mirror.
|
|
227
|
+
self._hydrated_session_ids.discard(session.id)
|
|
228
|
+
self._unsafe_live_session_ids.add(session.id)
|
|
229
|
+
await self.fallback.delete_session(session.id)
|
|
230
|
+
existing = None
|
|
125
231
|
if existing is None:
|
|
126
232
|
await self.fallback.create_session(
|
|
127
233
|
session.agent_id,
|
|
128
234
|
session.user_id,
|
|
129
235
|
session_id=session.id,
|
|
130
236
|
)
|
|
131
|
-
|
|
132
|
-
existing_ids =
|
|
133
|
-
for event in sorted(session.events, key=lambda item: item.seq_id):
|
|
237
|
+
await self.fallback.align_session_created_at(session.id, session.created_at)
|
|
238
|
+
existing_ids = await self.fallback.get_session_event_ids(session.id)
|
|
239
|
+
for index, event in enumerate(sorted(session.events, key=lambda item: item.seq_id)):
|
|
134
240
|
if event.id not in existing_ids:
|
|
135
241
|
await self.fallback.append_event(session.id, event)
|
|
242
|
+
if (index + 1) % 128 == 0:
|
|
243
|
+
# Uncontended in-memory awaits do not yield. Let history
|
|
244
|
+
# reads and driver cancellation run during a cold replay;
|
|
245
|
+
# the per-session hydrate lock still protects the merge.
|
|
246
|
+
await asyncio.sleep(0)
|
|
136
247
|
await self.fallback.update_session_metadata(
|
|
137
248
|
session.id,
|
|
138
249
|
title=session.title,
|
|
@@ -141,7 +252,7 @@ class ResilientSessionService(BaseSessionService):
|
|
|
141
252
|
first_prompt=session.first_prompt,
|
|
142
253
|
last_prompt=session.last_prompt,
|
|
143
254
|
)
|
|
144
|
-
current = await self.fallback.
|
|
255
|
+
current = await self.fallback.get_session_metadata(session.id)
|
|
145
256
|
if current is not None and session.state != current.state:
|
|
146
257
|
await self.fallback.update_state(
|
|
147
258
|
agent_id=session.agent_id,
|
|
@@ -153,6 +264,11 @@ class ResilientSessionService(BaseSessionService):
|
|
|
153
264
|
hydrated = await self.fallback.get_session(session.id)
|
|
154
265
|
if hydrated is None:
|
|
155
266
|
raise RuntimeError(f"Failed to hydrate live session {session.id}")
|
|
267
|
+
# The clean durable row defines the incarnation. The fallback
|
|
268
|
+
# mirror may have been created a moment earlier with another clock.
|
|
269
|
+
hydrated.created_at = session.created_at
|
|
270
|
+
self._unsafe_live_session_ids.discard(session.id)
|
|
271
|
+
self._hydrated_session_ids.add(session.id)
|
|
156
272
|
return hydrated
|
|
157
273
|
|
|
158
274
|
async def create_session(
|
|
@@ -161,12 +277,23 @@ class ResilientSessionService(BaseSessionService):
|
|
|
161
277
|
user_id: str,
|
|
162
278
|
session_id: Optional[str] = None,
|
|
163
279
|
) -> Session:
|
|
280
|
+
if session_id in self._deleted_session_ids:
|
|
281
|
+
raise SessionBackendUnavailable("Session deletion is pending durable recovery")
|
|
164
282
|
if session_id:
|
|
283
|
+
if session_id in self._dirty_session_ids:
|
|
284
|
+
existing = await self.fallback.get_session(session_id)
|
|
285
|
+
if existing is not None:
|
|
286
|
+
return existing
|
|
287
|
+
generation = self._live_generation.get(session_id, 0)
|
|
165
288
|
ok, durable = await self._call_primary("get_session", session_id)
|
|
166
289
|
if ok and durable is not None:
|
|
167
|
-
|
|
290
|
+
hydrated = await self._hydrate(durable, generation)
|
|
291
|
+
if hydrated is not None:
|
|
292
|
+
return hydrated
|
|
168
293
|
existing = await self.fallback.get_session(session_id)
|
|
169
294
|
if existing is not None:
|
|
295
|
+
if session_id in self._unsafe_live_session_ids:
|
|
296
|
+
self._clean_read_unavailable()
|
|
170
297
|
return existing
|
|
171
298
|
|
|
172
299
|
live = await self.fallback.create_session(agent_id, user_id, session_id=session_id)
|
|
@@ -178,21 +305,126 @@ class ResilientSessionService(BaseSessionService):
|
|
|
178
305
|
)
|
|
179
306
|
if ok and durable is not None:
|
|
180
307
|
self._primary_session_ids.add(durable.id)
|
|
181
|
-
|
|
308
|
+
# create_session may have returned an existing row from a racing
|
|
309
|
+
# instance. Its metadata projection does not prove the event log
|
|
310
|
+
# is empty, so establish a complete live mirror before marking it
|
|
311
|
+
# safe for degraded writes.
|
|
312
|
+
generation = self._live_generation.get(durable.id, 0)
|
|
313
|
+
ok, complete = await self._call_primary("get_session", durable.id)
|
|
314
|
+
if not ok or complete is None:
|
|
315
|
+
self._clean_read_unavailable()
|
|
316
|
+
hydrated = await self._hydrate(complete, generation)
|
|
317
|
+
if hydrated is None:
|
|
318
|
+
self._clean_read_unavailable()
|
|
319
|
+
return hydrated
|
|
320
|
+
self._mark_dirty(live.id)
|
|
182
321
|
return live
|
|
183
322
|
|
|
184
323
|
async def get_session(self, session_id: str) -> Optional[Session]:
|
|
185
|
-
|
|
324
|
+
if session_id in self._deleted_session_ids:
|
|
325
|
+
return None
|
|
326
|
+
if session_id in self._dirty_session_ids:
|
|
327
|
+
return await self.fallback.get_session(session_id)
|
|
328
|
+
if not self._primary_enabled:
|
|
329
|
+
if self._has_complete_live_session(session_id):
|
|
330
|
+
return await self.fallback.get_session(session_id)
|
|
331
|
+
self._clean_read_unavailable()
|
|
332
|
+
generation = self._live_generation.get(session_id, 0)
|
|
186
333
|
ok, durable = await self._call_primary("get_session", session_id)
|
|
187
334
|
if ok and durable is not None:
|
|
188
|
-
return await self._hydrate(durable)
|
|
189
|
-
|
|
335
|
+
return await self._hydrate(durable, generation)
|
|
336
|
+
if not ok:
|
|
337
|
+
if self._has_complete_live_session(session_id):
|
|
338
|
+
return await self.fallback.get_session(session_id)
|
|
339
|
+
self._clean_read_unavailable()
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
async def get_adk_session_snapshot(
|
|
343
|
+
self, session_id: str, expected_session: Session | None = None
|
|
344
|
+
) -> Optional[Session]:
|
|
345
|
+
if session_id in self._deleted_session_ids:
|
|
346
|
+
raise HistoryReloadRequired("Session changed during legacy read")
|
|
347
|
+
live = session_id in self._dirty_session_ids or not self._primary_enabled
|
|
348
|
+
if (not self._primary_enabled and session_id not in self._dirty_session_ids
|
|
349
|
+
and not self._has_complete_live_session(session_id)):
|
|
350
|
+
self._clean_read_unavailable()
|
|
351
|
+
backend = self.fallback if live else self.primary
|
|
352
|
+
snapshot = getattr(backend, "get_adk_session_snapshot", None)
|
|
353
|
+
if callable(snapshot):
|
|
354
|
+
session = await snapshot(session_id, expected_session)
|
|
355
|
+
else:
|
|
356
|
+
session = await backend.get_session(session_id)
|
|
357
|
+
if expected_session is not None and (
|
|
358
|
+
session is None or session.agent_id != expected_session.agent_id
|
|
359
|
+
or session.user_id != expected_session.user_id
|
|
360
|
+
or session.created_at != expected_session.created_at
|
|
361
|
+
):
|
|
362
|
+
raise HistoryReloadRequired("Session changed during legacy read")
|
|
363
|
+
if (session_id in self._deleted_session_ids
|
|
364
|
+
or (session_id in self._dirty_session_ids or not self._primary_enabled) != live):
|
|
365
|
+
raise HistoryReloadRequired("Session source changed during legacy read")
|
|
366
|
+
return session
|
|
190
367
|
|
|
191
368
|
async def get_session_metadata(self, session_id: str) -> Optional[Session]:
|
|
369
|
+
if session_id in self._deleted_session_ids:
|
|
370
|
+
return None
|
|
371
|
+
if session_id in self._dirty_session_ids:
|
|
372
|
+
return await self.fallback.get_session_metadata(session_id)
|
|
373
|
+
if not self._primary_enabled:
|
|
374
|
+
if self._has_complete_live_session(session_id):
|
|
375
|
+
return await self.fallback.get_session_metadata(session_id)
|
|
376
|
+
self._clean_read_unavailable()
|
|
192
377
|
ok, durable = await self._call_primary("get_session_metadata", session_id)
|
|
193
378
|
if ok and durable is not None:
|
|
194
379
|
return cast(Session, durable)
|
|
195
|
-
|
|
380
|
+
if not ok:
|
|
381
|
+
if self._has_complete_live_session(session_id):
|
|
382
|
+
return await self.fallback.get_session_metadata(session_id)
|
|
383
|
+
self._clean_read_unavailable()
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
async def get_session_header(self, session_id: str) -> Optional[Session]:
|
|
387
|
+
if session_id in self._deleted_session_ids:
|
|
388
|
+
return None
|
|
389
|
+
if session_id in self._dirty_session_ids:
|
|
390
|
+
return await self.fallback.get_session_header(session_id)
|
|
391
|
+
if not self._primary_enabled:
|
|
392
|
+
if self._has_complete_live_session(session_id):
|
|
393
|
+
return await self.fallback.get_session_header(session_id)
|
|
394
|
+
self._clean_read_unavailable()
|
|
395
|
+
method = "get_session_header" if hasattr(self.primary, "get_session_header") else "get_session_metadata"
|
|
396
|
+
ok, header = await self._call_primary(method, session_id)
|
|
397
|
+
if not ok:
|
|
398
|
+
if self._has_complete_live_session(session_id):
|
|
399
|
+
return await self.fallback.get_session_header(session_id)
|
|
400
|
+
self._clean_read_unavailable()
|
|
401
|
+
return cast(Optional[Session], header)
|
|
402
|
+
|
|
403
|
+
async def get_session_summary_events(self, session_id: str) -> list[SessionEvent]:
|
|
404
|
+
if session_id in self._deleted_session_ids:
|
|
405
|
+
return []
|
|
406
|
+
if session_id in self._dirty_session_ids:
|
|
407
|
+
return await self.fallback.get_session_summary_events(session_id)
|
|
408
|
+
if not self._primary_enabled:
|
|
409
|
+
if self._has_complete_live_session(session_id):
|
|
410
|
+
return await self.fallback.get_session_summary_events(session_id)
|
|
411
|
+
self._clean_read_unavailable()
|
|
412
|
+
ok, events = await self._call_primary("get_session_summary_events", session_id)
|
|
413
|
+
if ok:
|
|
414
|
+
return cast(list[SessionEvent], events)
|
|
415
|
+
if self._has_complete_live_session(session_id):
|
|
416
|
+
return await self.fallback.get_session_summary_events(session_id)
|
|
417
|
+
self._clean_read_unavailable()
|
|
418
|
+
|
|
419
|
+
async def get_max_seq_id(self, session_id: str) -> int:
|
|
420
|
+
if session_id in self._dirty_session_ids:
|
|
421
|
+
return await self.fallback.get_max_seq_id(session_id)
|
|
422
|
+
if not self._primary_enabled:
|
|
423
|
+
self._clean_read_unavailable()
|
|
424
|
+
ok, value = await self._call_primary("get_max_seq_id", session_id)
|
|
425
|
+
if not ok:
|
|
426
|
+
self._clean_read_unavailable()
|
|
427
|
+
return int(value or 0)
|
|
196
428
|
|
|
197
429
|
async def list_sessions(
|
|
198
430
|
self,
|
|
@@ -201,28 +433,96 @@ class ResilientSessionService(BaseSessionService):
|
|
|
201
433
|
offset: Optional[int] = None,
|
|
202
434
|
limit: Optional[int] = None,
|
|
203
435
|
) -> list[Session]:
|
|
436
|
+
dirty = bool(self._dirty_session_ids or self._deleted_session_ids)
|
|
437
|
+
bounded = limit is not None
|
|
438
|
+
# Fetch enough durable rows to replace every locally changed/deleted
|
|
439
|
+
# row ahead of the requested page, without loading the whole agent.
|
|
440
|
+
durable_limit = (
|
|
441
|
+
(offset or 0) + limit + len(self._dirty_session_ids)
|
|
442
|
+
+ len(self._deleted_session_ids)
|
|
443
|
+
if dirty and bounded else limit
|
|
444
|
+
)
|
|
204
445
|
ok, durable_sessions = await self._call_primary(
|
|
205
446
|
"list_sessions",
|
|
206
447
|
agent_id,
|
|
207
448
|
user_id,
|
|
208
|
-
offset,
|
|
209
|
-
|
|
449
|
+
0 if dirty and bounded else (None if dirty else offset),
|
|
450
|
+
durable_limit,
|
|
210
451
|
)
|
|
211
|
-
if ok:
|
|
212
|
-
return cast(list[Session], durable_sessions or [])
|
|
213
|
-
|
|
452
|
+
if ok and not dirty:
|
|
453
|
+
return [s for s in cast(list[Session], durable_sessions or [])
|
|
454
|
+
if s.id not in self._deleted_session_ids]
|
|
455
|
+
if not ok:
|
|
456
|
+
self._clean_read_unavailable()
|
|
457
|
+
# Dirty rows may have an older durable counterpart. Merge metadata only.
|
|
458
|
+
durable = {s.id: s for s in (durable_sessions or [])} if ok else {}
|
|
459
|
+
if bounded and ok:
|
|
460
|
+
live = [session for session in await asyncio.gather(*(
|
|
461
|
+
self.fallback.get_session_metadata(session_id)
|
|
462
|
+
for session_id in self._dirty_session_ids
|
|
463
|
+
)) if session is not None]
|
|
464
|
+
else:
|
|
465
|
+
live = await self.fallback.list_session_metadata(agent_id, user_id)
|
|
466
|
+
durable.update({s.id: s for s in live
|
|
467
|
+
if s.agent_id == agent_id
|
|
468
|
+
and (user_id is None or s.user_id == user_id)
|
|
469
|
+
and (not ok or s.id in self._dirty_session_ids)})
|
|
470
|
+
ordered = sorted((s for s in durable.values() if s.id not in self._deleted_session_ids),
|
|
471
|
+
key=lambda s: (s.updated_at, s.created_at, s.id), reverse=True)
|
|
472
|
+
start = offset or 0
|
|
473
|
+
return ordered[start:None if limit is None else start + limit]
|
|
214
474
|
|
|
215
475
|
async def count_sessions(self, agent_id: str, user_id: Optional[str] = None) -> int:
|
|
216
476
|
ok, total = await self._call_primary("count_sessions", agent_id, user_id)
|
|
217
|
-
if ok:
|
|
477
|
+
if ok and not (self._dirty_session_ids or self._deleted_session_ids):
|
|
218
478
|
return int(total or 0)
|
|
219
|
-
|
|
479
|
+
if not ok:
|
|
480
|
+
self._clean_read_unavailable()
|
|
481
|
+
count = int(total or 0)
|
|
482
|
+
for session_id in self._dirty_session_ids | self._deleted_session_ids:
|
|
483
|
+
ok, durable = await self._call_primary("get_session_header", session_id)
|
|
484
|
+
if not ok:
|
|
485
|
+
self._clean_read_unavailable()
|
|
486
|
+
live = (await self.fallback.get_session_header(session_id)
|
|
487
|
+
if session_id not in self._deleted_session_ids else None)
|
|
488
|
+
durable_in_scope = (durable is not None and durable.agent_id == agent_id
|
|
489
|
+
and (user_id is None or durable.user_id == user_id))
|
|
490
|
+
live_in_scope = (live is not None and live.agent_id == agent_id
|
|
491
|
+
and (user_id is None or live.user_id == user_id))
|
|
492
|
+
count += int(live_in_scope) - int(durable_in_scope)
|
|
493
|
+
return count
|
|
220
494
|
|
|
221
495
|
async def delete_session(self, session_id: str) -> bool:
|
|
496
|
+
budget = getattr(self.primary, "db_budget", session_db_budget())
|
|
497
|
+
deadline = DatabaseOperationDeadline(budget)
|
|
498
|
+
lock = self._hydrate_locks.setdefault(session_id, asyncio.Lock())
|
|
499
|
+
await asyncio.wait_for(
|
|
500
|
+
lock.acquire(), timeout=min(budget.lock_seconds, deadline.remaining_seconds()),
|
|
501
|
+
)
|
|
502
|
+
try:
|
|
503
|
+
deleted = await deadline.run(self._delete_session_locked(session_id))
|
|
504
|
+
self._live_generation[session_id] = self._live_generation.get(session_id, 0) + 1
|
|
505
|
+
return deleted
|
|
506
|
+
finally:
|
|
507
|
+
lock.release()
|
|
508
|
+
|
|
509
|
+
async def _delete_session_locked(self, session_id: str) -> bool:
|
|
222
510
|
deleted = await self.fallback.delete_session(session_id)
|
|
223
|
-
|
|
511
|
+
try:
|
|
512
|
+
ok, durable_deleted = await self._call_primary("delete_session", session_id)
|
|
513
|
+
except BaseException:
|
|
514
|
+
# Cancellation after the local delete cannot let a recovered
|
|
515
|
+
# durable row reappear in this process.
|
|
516
|
+
self._deleted_session_ids.add(session_id)
|
|
517
|
+
raise
|
|
224
518
|
if ok:
|
|
225
519
|
self._primary_session_ids.discard(session_id)
|
|
520
|
+
self._dirty_session_ids.discard(session_id)
|
|
521
|
+
self._hydrated_session_ids.discard(session_id)
|
|
522
|
+
self._unsafe_live_session_ids.discard(session_id)
|
|
523
|
+
self._deleted_session_ids.discard(session_id)
|
|
524
|
+
else:
|
|
525
|
+
self._deleted_session_ids.add(session_id)
|
|
226
526
|
return deleted or bool(durable_deleted) if ok else deleted
|
|
227
527
|
|
|
228
528
|
async def update_session_metadata(
|
|
@@ -235,27 +535,39 @@ class ResilientSessionService(BaseSessionService):
|
|
|
235
535
|
first_prompt: Optional[str] = None,
|
|
236
536
|
last_prompt: Optional[str] = None,
|
|
237
537
|
) -> Session:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
title_source=title_source,
|
|
244
|
-
summary=summary,
|
|
245
|
-
first_prompt=first_prompt,
|
|
246
|
-
last_prompt=last_prompt,
|
|
538
|
+
budget = getattr(self.primary, "db_budget", session_db_budget())
|
|
539
|
+
deadline = DatabaseOperationDeadline(budget)
|
|
540
|
+
lock = self._hydrate_locks.setdefault(session_id, asyncio.Lock())
|
|
541
|
+
await asyncio.wait_for(
|
|
542
|
+
lock.acquire(), timeout=min(budget.lock_seconds, deadline.remaining_seconds()),
|
|
247
543
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
544
|
+
try:
|
|
545
|
+
updated = await deadline.run(self._update_session_metadata_locked(
|
|
546
|
+
session_id, title=title, title_source=title_source,
|
|
547
|
+
summary=summary, first_prompt=first_prompt, last_prompt=last_prompt,
|
|
548
|
+
))
|
|
549
|
+
self._live_generation[session_id] = self._live_generation.get(session_id, 0) + 1
|
|
550
|
+
return updated
|
|
551
|
+
finally:
|
|
552
|
+
lock.release()
|
|
553
|
+
|
|
554
|
+
async def _update_session_metadata_locked(
|
|
555
|
+
self, session_id: str, *, title: Optional[str] = None,
|
|
556
|
+
title_source: Optional[str] = None, summary: Optional[str] = None,
|
|
557
|
+
first_prompt: Optional[str] = None, last_prompt: Optional[str] = None,
|
|
558
|
+
) -> Session:
|
|
559
|
+
kwargs = dict(title=title, title_source=title_source, summary=summary,
|
|
560
|
+
first_prompt=first_prompt, last_prompt=last_prompt)
|
|
561
|
+
if session_id not in self._dirty_session_ids:
|
|
562
|
+
ok, durable = await self._call_primary("update_session_metadata", session_id, **kwargs)
|
|
563
|
+
if ok:
|
|
564
|
+
if session_id in self._hydrated_session_ids:
|
|
565
|
+
await self.fallback.update_session_metadata(session_id, **kwargs)
|
|
566
|
+
return cast(Session, durable)
|
|
567
|
+
if session_id not in self._hydrated_session_ids or session_id in self._unsafe_live_session_ids:
|
|
568
|
+
self._clean_read_unavailable()
|
|
569
|
+
self._mark_dirty(session_id)
|
|
570
|
+
return await self.fallback.update_session_metadata(session_id, **kwargs)
|
|
259
571
|
|
|
260
572
|
async def _ensure_primary_session(self, session_id: str) -> None:
|
|
261
573
|
"""Create the session in PG if it only exists in memory (degraded-era)."""
|
|
@@ -264,11 +576,11 @@ class ResilientSessionService(BaseSessionService):
|
|
|
264
576
|
async with self._primary_session_lock:
|
|
265
577
|
if not self._primary_enabled or session_id in self._primary_session_ids:
|
|
266
578
|
return
|
|
267
|
-
ok, durable = await self._call_primary("
|
|
579
|
+
ok, durable = await self._call_primary("get_session_metadata", session_id)
|
|
268
580
|
if not ok:
|
|
269
581
|
return
|
|
270
582
|
if durable is None:
|
|
271
|
-
live = await self.fallback.
|
|
583
|
+
live = await self.fallback.get_session_metadata(session_id)
|
|
272
584
|
if live is None:
|
|
273
585
|
return
|
|
274
586
|
ok, durable = await self._call_primary(
|
|
@@ -282,431 +594,181 @@ class ResilientSessionService(BaseSessionService):
|
|
|
282
594
|
self._primary_session_ids.add(session_id)
|
|
283
595
|
|
|
284
596
|
async def append_event(self, session_id: str, event: SessionEvent) -> SessionEvent:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
self,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
*,
|
|
302
|
-
after_seq_id: Optional[int] = None,
|
|
303
|
-
before_seq_id: Optional[int] = None,
|
|
304
|
-
) -> list[SessionEvent]:
|
|
305
|
-
# ResilientSessionService is explicitly live-first: hydrate any durable
|
|
306
|
-
# prefix, then read the indexed in-memory authority used by get_events.
|
|
307
|
-
await self.get_session(session_id)
|
|
308
|
-
return await self.fallback.get_events_by_invocation_id(
|
|
309
|
-
session_id,
|
|
310
|
-
invocation_id,
|
|
311
|
-
after_seq_id=after_seq_id,
|
|
312
|
-
before_seq_id=before_seq_id,
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
async def get_events(
|
|
316
|
-
self,
|
|
317
|
-
session_id: str,
|
|
318
|
-
offset: Optional[int] = None,
|
|
319
|
-
limit: Optional[int] = None,
|
|
320
|
-
after_seq_id: Optional[int] = None,
|
|
321
|
-
before_seq_id: Optional[int] = None,
|
|
322
|
-
) -> list[SessionEvent]:
|
|
323
|
-
await self.get_session(session_id)
|
|
324
|
-
return cast(
|
|
325
|
-
list[SessionEvent],
|
|
326
|
-
await self.fallback.get_events(
|
|
327
|
-
session_id,
|
|
328
|
-
offset,
|
|
329
|
-
limit,
|
|
330
|
-
after_seq_id,
|
|
331
|
-
before_seq_id,
|
|
332
|
-
),
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
async def count_events(
|
|
336
|
-
self,
|
|
337
|
-
session_id: str,
|
|
338
|
-
after_seq_id: Optional[int] = None,
|
|
339
|
-
before_seq_id: Optional[int] = None,
|
|
340
|
-
) -> int:
|
|
341
|
-
await self.get_session(session_id)
|
|
342
|
-
return cast(
|
|
343
|
-
int,
|
|
344
|
-
await self.fallback.count_events(session_id, after_seq_id, before_seq_id),
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
async def get_sessions_by_ids(self, session_ids: list[str]) -> list[Session]:
|
|
348
|
-
result: dict[str, Session] = {}
|
|
349
|
-
ok, durable = await self._call_primary("get_sessions_by_ids", session_ids)
|
|
350
|
-
if ok:
|
|
351
|
-
result.update({session.id: session for session in durable or []})
|
|
352
|
-
live = await self.fallback.get_sessions_by_ids(session_ids)
|
|
353
|
-
result.update({session.id: session for session in live})
|
|
354
|
-
return [result[session_id] for session_id in session_ids if session_id in result]
|
|
355
|
-
|
|
356
|
-
async def list_session_metadata(
|
|
357
|
-
self, agent_id: Optional[str] = None, user_id: Optional[str] = None
|
|
358
|
-
) -> list[Session]:
|
|
359
|
-
result: dict[str, Session] = {}
|
|
360
|
-
ok, durable = await self._call_primary("list_session_metadata", agent_id, user_id)
|
|
361
|
-
if ok:
|
|
362
|
-
result.update({session.id: session for session in durable or []})
|
|
363
|
-
live = await self.fallback.list_session_metadata(agent_id, user_id)
|
|
364
|
-
result.update({session.id: session for session in live})
|
|
365
|
-
sessions = list(result.values())
|
|
366
|
-
sessions.sort(key=lambda item: (item.updated_at, item.created_at, item.id), reverse=True)
|
|
367
|
-
return sessions
|
|
368
|
-
|
|
369
|
-
async def _partition_query_session_ids(
|
|
370
|
-
self, session_ids: list[str] | None, agent_id: str | None
|
|
371
|
-
) -> tuple[list[str], list[str]]:
|
|
372
|
-
if session_ids is None:
|
|
373
|
-
sessions = await self.list_session_metadata(agent_id)
|
|
374
|
-
ids = [session.id for session in sessions]
|
|
375
|
-
else:
|
|
376
|
-
ids = list(dict.fromkeys(session_ids))
|
|
377
|
-
dirty = set(self._dirty_session_ids)
|
|
378
|
-
durable_ids: set[str] = set()
|
|
379
|
-
if ids:
|
|
380
|
-
ok, durable_metadata = await self._call_primary("get_sessions_by_ids", ids)
|
|
381
|
-
if ok:
|
|
382
|
-
durable_ids = {session.id for session in durable_metadata or []}
|
|
383
|
-
live_metadata = await self.fallback.get_sessions_by_ids(ids)
|
|
384
|
-
live_ids = {session.id for session in live_metadata}
|
|
385
|
-
clean_ids = [
|
|
386
|
-
session_id
|
|
387
|
-
for session_id in ids
|
|
388
|
-
if session_id not in dirty and session_id in durable_ids
|
|
389
|
-
]
|
|
390
|
-
fallback_ids = [
|
|
391
|
-
session_id for session_id in ids if session_id in dirty or session_id in live_ids
|
|
392
|
-
]
|
|
393
|
-
return clean_ids, fallback_ids
|
|
394
|
-
|
|
395
|
-
async def query_events(self, query: SessionEventQuery) -> list[SessionEvent]:
|
|
396
|
-
clean_ids, live_ids = await self._partition_query_session_ids(
|
|
397
|
-
query.session_ids, query.agent_id
|
|
398
|
-
)
|
|
399
|
-
window = query.offset + query.limit
|
|
400
|
-
durable_events: list[SessionEvent] = []
|
|
401
|
-
if clean_ids:
|
|
402
|
-
ok, result = await self._call_primary(
|
|
403
|
-
"query_events",
|
|
404
|
-
SessionEventQuery(
|
|
405
|
-
**{**query.__dict__, "session_ids": clean_ids, "offset": 0, "limit": window}
|
|
406
|
-
),
|
|
407
|
-
)
|
|
408
|
-
if ok:
|
|
409
|
-
durable_events = cast(list[SessionEvent], result or [])
|
|
410
|
-
else:
|
|
411
|
-
return await self.fallback.query_events(query)
|
|
412
|
-
live_events = (
|
|
413
|
-
await self.fallback.query_events(
|
|
414
|
-
SessionEventQuery(
|
|
415
|
-
**{**query.__dict__, "session_ids": live_ids, "offset": 0, "limit": window}
|
|
416
|
-
)
|
|
417
|
-
)
|
|
418
|
-
if live_ids
|
|
419
|
-
else []
|
|
597
|
+
return await self._append_event_entry(session_id, event, None)
|
|
598
|
+
|
|
599
|
+
async def append_event_if_session_matches(
|
|
600
|
+
self, session_id: str, event: SessionEvent, expected_session: Session
|
|
601
|
+
) -> SessionEvent:
|
|
602
|
+
"""Use normal recovery fencing with an incarnation check at each write."""
|
|
603
|
+
return await self._append_event_entry(session_id, event, expected_session)
|
|
604
|
+
|
|
605
|
+
async def _append_event_entry(
|
|
606
|
+
self, session_id: str, event: SessionEvent, expected_session: Session | None
|
|
607
|
+
) -> SessionEvent:
|
|
608
|
+
budget = getattr(self.primary, "db_budget", session_db_budget())
|
|
609
|
+
deadline = DatabaseOperationDeadline(budget)
|
|
610
|
+
lock = self._hydrate_locks.setdefault(session_id, asyncio.Lock())
|
|
611
|
+
await asyncio.wait_for(
|
|
612
|
+
lock.acquire(), timeout=min(budget.lock_seconds, deadline.remaining_seconds()),
|
|
420
613
|
)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
else:
|
|
426
|
-
merged.sort(
|
|
427
|
-
key=lambda event: (event.timestamp, event.session_id, event.seq_id, event.id)
|
|
428
|
-
)
|
|
429
|
-
if query.from_start:
|
|
430
|
-
return merged[query.offset : query.offset + query.limit]
|
|
431
|
-
end = max(len(merged) - query.offset, 0)
|
|
432
|
-
return merged[max(end - query.limit, 0) : end]
|
|
433
|
-
|
|
434
|
-
async def count_event_query(self, query: SessionEventQuery) -> int:
|
|
435
|
-
# Deduplicate by physical event id because a hydrated clean session may
|
|
436
|
-
# be present in both children during recovery.
|
|
437
|
-
events = await self.query_events(
|
|
438
|
-
SessionEventQuery(
|
|
439
|
-
**{
|
|
440
|
-
**query.__dict__,
|
|
441
|
-
"offset": 0,
|
|
442
|
-
"limit": 2**31 - 1,
|
|
443
|
-
"from_start": True,
|
|
444
|
-
}
|
|
445
|
-
)
|
|
446
|
-
)
|
|
447
|
-
return len(events)
|
|
448
|
-
|
|
449
|
-
async def get_checkpoint_lookup_stats(
|
|
450
|
-
self, session_id: str, run_id: str, checkpoint_id: str
|
|
451
|
-
) -> dict[str, object]:
|
|
452
|
-
if session_id not in self._dirty_session_ids:
|
|
453
|
-
ok, stats = await self._call_primary(
|
|
454
|
-
"get_checkpoint_lookup_stats", session_id, run_id, checkpoint_id
|
|
455
|
-
)
|
|
456
|
-
if ok:
|
|
457
|
-
return cast(dict[str, object], stats)
|
|
458
|
-
return await self.fallback.get_checkpoint_lookup_stats(
|
|
459
|
-
session_id, run_id, checkpoint_id
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
async def scan_checkpoint_events(
|
|
463
|
-
self, query: CheckpointEventQuery
|
|
464
|
-
) -> list[SessionEvent]:
|
|
465
|
-
if query.limit < 1 or query.limit > 50:
|
|
466
|
-
raise ValueError("checkpoint scan limit must be between 1 and 50")
|
|
467
|
-
clean_ids, live_ids = await self._partition_query_session_ids(
|
|
468
|
-
query.session_ids, query.agent_id
|
|
469
|
-
)
|
|
470
|
-
if clean_ids and not live_ids:
|
|
471
|
-
ok, result = await self._call_primary(
|
|
472
|
-
"scan_checkpoint_events",
|
|
473
|
-
CheckpointEventQuery(**{**query.__dict__, "session_ids": clean_ids}),
|
|
474
|
-
)
|
|
475
|
-
if ok:
|
|
476
|
-
return cast(list[SessionEvent], result or [])
|
|
477
|
-
return await self.fallback.scan_checkpoint_events(query)
|
|
478
|
-
if live_ids and not clean_ids:
|
|
479
|
-
return await self.fallback.scan_checkpoint_events(
|
|
480
|
-
CheckpointEventQuery(**{**query.__dict__, "session_ids": live_ids})
|
|
481
|
-
)
|
|
482
|
-
window = query.offset + query.limit
|
|
483
|
-
durable: list[SessionEvent] = []
|
|
484
|
-
if clean_ids:
|
|
485
|
-
ok, result = await self._call_primary(
|
|
486
|
-
"scan_checkpoint_events",
|
|
487
|
-
CheckpointEventQuery(
|
|
488
|
-
**{
|
|
489
|
-
**query.__dict__,
|
|
490
|
-
"session_ids": clean_ids,
|
|
491
|
-
"offset": 0,
|
|
492
|
-
"limit": min(window, 50),
|
|
493
|
-
}
|
|
494
|
-
),
|
|
614
|
+
clean_write = session_id not in self._dirty_session_ids
|
|
615
|
+
try:
|
|
616
|
+
persisted = await deadline.run(
|
|
617
|
+
self._append_event_locked(session_id, event, deadline, expected_session)
|
|
495
618
|
)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
"limit": min(window, 50),
|
|
508
|
-
}
|
|
619
|
+
self._live_generation[session_id] = self._live_generation.get(session_id, 0) + 1
|
|
620
|
+
return persisted
|
|
621
|
+
except (TimeoutError, asyncio.CancelledError, SessionBackendUnavailable) as error:
|
|
622
|
+
if clean_write:
|
|
623
|
+
# The primary may have committed before its acknowledgement or
|
|
624
|
+
# stable-ID readback was interrupted. Fence the old mirror and
|
|
625
|
+
# any hydration that captured a snapshot before this write.
|
|
626
|
+
self._unsafe_live_session_ids.add(session_id)
|
|
627
|
+
self._hydrated_session_ids.discard(session_id)
|
|
628
|
+
self._hydrated_state_keys.difference_update(
|
|
629
|
+
key for key in tuple(self._hydrated_state_keys) if key[3] == session_id
|
|
509
630
|
)
|
|
510
|
-
|
|
511
|
-
if
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return merged[query.offset : query.offset + query.limit]
|
|
517
|
-
|
|
518
|
-
async def iter_checkpoint_event_chunks(
|
|
519
|
-
self, query: CheckpointEventQuery
|
|
520
|
-
) -> AsyncIterator[list[SessionEvent]]:
|
|
521
|
-
if query.limit < 1 or query.limit > 50:
|
|
522
|
-
raise ValueError("checkpoint scan limit must be between 1 and 50")
|
|
523
|
-
clean_ids, live_ids = await self._partition_query_session_ids(
|
|
524
|
-
query.session_ids, query.agent_id
|
|
525
|
-
)
|
|
526
|
-
token = self._checkpoint_partition.set(
|
|
527
|
-
(frozenset(clean_ids), frozenset(live_ids))
|
|
528
|
-
)
|
|
529
|
-
batches = self._iter_checkpoint_event_chunks_partitioned(
|
|
530
|
-
query, clean_ids, live_ids
|
|
531
|
-
).__aiter__()
|
|
532
|
-
try:
|
|
533
|
-
async for batch in batches:
|
|
534
|
-
yield batch
|
|
631
|
+
self._live_generation[session_id] = self._live_generation.get(session_id, 0) + 1
|
|
632
|
+
if isinstance(error, TimeoutError):
|
|
633
|
+
raise SessionBackendUnavailable(
|
|
634
|
+
"Session event commit outcome is unknown; stable ID confirmation required"
|
|
635
|
+
) from error
|
|
636
|
+
raise
|
|
535
637
|
finally:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
self
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
638
|
+
lock.release()
|
|
639
|
+
|
|
640
|
+
async def _append_event_locked(
|
|
641
|
+
self, session_id: str, event: SessionEvent, deadline: DatabaseOperationDeadline,
|
|
642
|
+
expected_session: Session | None = None,
|
|
643
|
+
) -> SessionEvent:
|
|
644
|
+
if expected_session is not None and session_id in self._deleted_session_ids:
|
|
645
|
+
raise HistoryReloadRequired("Session changed during legacy import")
|
|
646
|
+
|
|
647
|
+
async def append_on(backend: BaseSessionService) -> SessionEvent:
|
|
648
|
+
if expected_session is None:
|
|
649
|
+
return await backend.append_event(session_id, event)
|
|
650
|
+
guarded = getattr(backend, "append_event_if_session_matches", None)
|
|
651
|
+
if callable(guarded):
|
|
652
|
+
return await guarded(session_id, event, expected_session)
|
|
653
|
+
current = await backend.get_session_metadata(session_id)
|
|
654
|
+
if (current is None or current.agent_id != expected_session.agent_id
|
|
655
|
+
or current.user_id != expected_session.user_id
|
|
656
|
+
or current.created_at != expected_session.created_at):
|
|
657
|
+
raise HistoryReloadRequired("Session changed during legacy import")
|
|
658
|
+
return await backend.append_event(session_id, event)
|
|
659
|
+
|
|
660
|
+
if session_id in self._dirty_session_ids:
|
|
661
|
+
return await append_on(self.fallback)
|
|
662
|
+
if not self._primary_enabled:
|
|
663
|
+
if not self._has_complete_live_session(session_id):
|
|
664
|
+
self._clean_read_unavailable()
|
|
665
|
+
self._mark_dirty(session_id)
|
|
666
|
+
return await append_on(self.fallback)
|
|
667
|
+
if expected_session is None:
|
|
668
|
+
ok, durable = await self._call_primary("append_event", session_id, event)
|
|
669
|
+
else:
|
|
565
670
|
try:
|
|
566
|
-
|
|
567
|
-
|
|
671
|
+
durable = await append_on(self.primary)
|
|
672
|
+
ok = True
|
|
568
673
|
except Exception as exc:
|
|
569
674
|
if not is_session_backend_failure(exc):
|
|
570
675
|
raise
|
|
571
676
|
self._disable_primary(exc)
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if callable(close):
|
|
578
|
-
await close()
|
|
579
|
-
return
|
|
580
|
-
|
|
581
|
-
iterators = {
|
|
582
|
-
"clean": self.primary.iter_checkpoint_event_chunks(
|
|
583
|
-
CheckpointEventQuery(
|
|
584
|
-
**{**query.__dict__, "session_ids": clean_ids, "offset": 0, "limit": 50}
|
|
585
|
-
)
|
|
586
|
-
).__aiter__(),
|
|
587
|
-
"live": self.fallback.iter_checkpoint_event_chunks(
|
|
588
|
-
CheckpointEventQuery(
|
|
589
|
-
**{**query.__dict__, "session_ids": live_ids, "offset": 0, "limit": 50}
|
|
590
|
-
)
|
|
591
|
-
).__aiter__(),
|
|
592
|
-
}
|
|
593
|
-
pages: dict[str, list[SessionEvent]] = {"clean": [], "live": []}
|
|
594
|
-
indexes = {"clean": 0, "live": 0}
|
|
595
|
-
exhausted = {"clean": False, "live": False}
|
|
596
|
-
skipped = 0
|
|
597
|
-
|
|
598
|
-
async def load(kind: str) -> None:
|
|
677
|
+
ok, durable = False, None
|
|
678
|
+
if not ok:
|
|
679
|
+
# The commit may have succeeded before the transport failed. A
|
|
680
|
+
# stable event ID is the only safe confirmation; never replay the
|
|
681
|
+
# append or its upstream tool/LLM side effect automatically.
|
|
599
682
|
try:
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
self, keys: list[tuple[str, str, str]]
|
|
649
|
-
) -> dict[str, object]:
|
|
650
|
-
if len(keys) > 50:
|
|
651
|
-
raise ValueError("checkpoint stats batch cannot exceed 50 keys")
|
|
652
|
-
unique_keys = list(dict.fromkeys(keys))
|
|
653
|
-
active = self._checkpoint_partition.get()
|
|
654
|
-
if active is None:
|
|
655
|
-
clean_ids, live_ids = await self._partition_query_session_ids(
|
|
656
|
-
list(dict.fromkeys(key[0] for key in unique_keys)), None
|
|
657
|
-
)
|
|
658
|
-
clean_set, live_set = set(clean_ids), set(live_ids)
|
|
683
|
+
if expected_session is not None:
|
|
684
|
+
guarded_read = getattr(
|
|
685
|
+
self.primary, "get_event_by_id_if_session_matches", None
|
|
686
|
+
)
|
|
687
|
+
if callable(guarded_read):
|
|
688
|
+
committed = await deadline.run(
|
|
689
|
+
guarded_read(session_id, event.id, expected_session)
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
current = await deadline.run(
|
|
693
|
+
self.primary.get_session_metadata(session_id)
|
|
694
|
+
)
|
|
695
|
+
if (current is None or current.agent_id != expected_session.agent_id
|
|
696
|
+
or current.user_id != expected_session.user_id
|
|
697
|
+
or current.created_at != expected_session.created_at):
|
|
698
|
+
raise HistoryReloadRequired("Session changed during legacy import")
|
|
699
|
+
committed = await deadline.run(
|
|
700
|
+
self.primary.get_event_by_id(session_id, event.id)
|
|
701
|
+
)
|
|
702
|
+
else:
|
|
703
|
+
committed = await deadline.run(
|
|
704
|
+
self.primary.get_event_by_id(session_id, event.id)
|
|
705
|
+
)
|
|
706
|
+
except HistoryReloadRequired:
|
|
707
|
+
raise
|
|
708
|
+
except Exception as error:
|
|
709
|
+
raise SessionBackendUnavailable(
|
|
710
|
+
"Session event commit outcome is unknown; stable ID confirmation failed"
|
|
711
|
+
) from error
|
|
712
|
+
if committed is not None:
|
|
713
|
+
self._primary_enabled = True
|
|
714
|
+
if self._probe_task is not None and not self._probe_task.done():
|
|
715
|
+
self._probe_task.cancel()
|
|
716
|
+
await self._mirror_persisted_event(session_id, event, committed)
|
|
717
|
+
return cast(SessionEvent, committed)
|
|
718
|
+
if session_id in self._unsafe_live_session_ids or session_id not in self._hydrated_session_ids:
|
|
719
|
+
self._clean_read_unavailable()
|
|
720
|
+
self._mark_dirty(session_id)
|
|
721
|
+
return await append_on(self.fallback)
|
|
722
|
+
persisted = cast(SessionEvent, durable)
|
|
723
|
+
await self._mirror_persisted_event(session_id, event, persisted)
|
|
724
|
+
return persisted
|
|
725
|
+
|
|
726
|
+
async def _mirror_persisted_event(
|
|
727
|
+
self, session_id: str, event: SessionEvent, persisted: SessionEvent,
|
|
728
|
+
) -> None:
|
|
729
|
+
if session_id in self._hydrated_session_ids:
|
|
730
|
+
expected_seq = await self.fallback.count_events(session_id) + 1
|
|
659
731
|
else:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
ok,
|
|
695
|
-
"
|
|
732
|
+
expected_seq = None
|
|
733
|
+
if expected_seq == persisted.seq_id:
|
|
734
|
+
if await self.fallback.get_event_by_id(session_id, event.id) is None:
|
|
735
|
+
await self.fallback.append_event(session_id, event)
|
|
736
|
+
elif expected_seq is not None:
|
|
737
|
+
self._unsafe_live_session_ids.add(session_id)
|
|
738
|
+
|
|
739
|
+
async def claim_tool_receipt(self, session_id: str, **kwargs: Any) -> ToolReceiptClaim:
|
|
740
|
+
# Falling back to the process-local live store here would re-open the
|
|
741
|
+
# cross-process duplicate window this claim closes. Fail closed while
|
|
742
|
+
# the durable primary is unavailable instead.
|
|
743
|
+
if (not self._primary_enabled or session_id in self._dirty_session_ids
|
|
744
|
+
or session_id in self._deleted_session_ids):
|
|
745
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
746
|
+
ok, result = await self._call_primary("claim_tool_receipt", session_id, **kwargs)
|
|
747
|
+
if not ok:
|
|
748
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
749
|
+
return cast(ToolReceiptClaim, result)
|
|
750
|
+
|
|
751
|
+
async def settle_tool_receipt(self, session_id: str, **kwargs: Any) -> ToolReceiptClaim:
|
|
752
|
+
if (not self._primary_enabled or session_id in self._dirty_session_ids
|
|
753
|
+
or session_id in self._deleted_session_ids):
|
|
754
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
755
|
+
ok, result = await self._call_primary("settle_tool_receipt", session_id, **kwargs)
|
|
756
|
+
if not ok:
|
|
757
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
758
|
+
return cast(ToolReceiptClaim, result)
|
|
759
|
+
|
|
760
|
+
async def get_tool_receipt_claim(
|
|
761
|
+
self, session_id: str, *, idempotency_key: str
|
|
762
|
+
) -> ToolReceiptClaim | None:
|
|
763
|
+
if (not self._primary_enabled or session_id in self._dirty_session_ids
|
|
764
|
+
or session_id in self._deleted_session_ids):
|
|
765
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
766
|
+
ok, result = await self._call_primary(
|
|
767
|
+
"get_tool_receipt_claim", session_id, idempotency_key=idempotency_key
|
|
696
768
|
)
|
|
697
|
-
if ok:
|
|
698
|
-
|
|
699
|
-
return
|
|
700
|
-
|
|
701
|
-
async def count_events_for_agent(
|
|
702
|
-
self,
|
|
703
|
-
agent_id: str,
|
|
704
|
-
user_id: Optional[str] = None,
|
|
705
|
-
) -> int:
|
|
706
|
-
ok, total = await self._call_primary("count_events_for_agent", agent_id, user_id)
|
|
707
|
-
if ok:
|
|
708
|
-
return int(total or 0)
|
|
709
|
-
return await self.fallback.count_events_for_agent(agent_id, user_id)
|
|
769
|
+
if not ok:
|
|
770
|
+
raise SessionBackendUnavailable("durable tool receipt store unavailable")
|
|
771
|
+
return cast(ToolReceiptClaim | None, result)
|
|
710
772
|
|
|
711
773
|
async def get_state(
|
|
712
774
|
self,
|
|
@@ -716,10 +778,31 @@ class ResilientSessionService(BaseSessionService):
|
|
|
716
778
|
scope: str = "session",
|
|
717
779
|
) -> Optional[SessionState]:
|
|
718
780
|
if session_id:
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
781
|
+
lock = self._hydrate_locks.setdefault(session_id, asyncio.Lock())
|
|
782
|
+
async with lock:
|
|
783
|
+
return await self._get_state_locked(agent_id, user_id, session_id, scope)
|
|
784
|
+
return await self._get_state_locked(agent_id, user_id, session_id, scope)
|
|
785
|
+
|
|
786
|
+
async def _get_state_locked(
|
|
787
|
+
self, agent_id: str, user_id: Optional[str], session_id: Optional[str],
|
|
788
|
+
scope: str,
|
|
789
|
+
) -> Optional[SessionState]:
|
|
790
|
+
state_key = (scope, agent_id, user_id or "", session_id or "")
|
|
791
|
+
unsafe_live = session_id in self._unsafe_live_session_ids
|
|
792
|
+
if not unsafe_live and (
|
|
793
|
+
state_key in self._dirty_state_keys or session_id in self._dirty_session_ids
|
|
794
|
+
):
|
|
795
|
+
return await self.fallback.get_state(agent_id, user_id, session_id, scope)
|
|
796
|
+
safe_live = not unsafe_live and (
|
|
797
|
+
state_key in self._hydrated_state_keys or (
|
|
798
|
+
scope == "session" and bool(session_id)
|
|
799
|
+
and self._has_complete_live_session(session_id)
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
if not self._primary_enabled:
|
|
803
|
+
if safe_live:
|
|
804
|
+
return await self.fallback.get_state(agent_id, user_id, session_id, scope)
|
|
805
|
+
self._clean_read_unavailable()
|
|
723
806
|
ok, durable = await self._call_primary(
|
|
724
807
|
"get_state",
|
|
725
808
|
agent_id,
|
|
@@ -728,14 +811,14 @@ class ResilientSessionService(BaseSessionService):
|
|
|
728
811
|
scope,
|
|
729
812
|
)
|
|
730
813
|
if ok and durable is not None:
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
)
|
|
738
|
-
return
|
|
814
|
+
await self.fallback.mirror_state_snapshot(cast(SessionState, durable))
|
|
815
|
+
self._hydrated_state_keys.add(state_key)
|
|
816
|
+
return cast(SessionState, durable)
|
|
817
|
+
if not ok:
|
|
818
|
+
if safe_live:
|
|
819
|
+
return await self.fallback.get_state(agent_id, user_id, session_id, scope)
|
|
820
|
+
self._clean_read_unavailable()
|
|
821
|
+
return None
|
|
739
822
|
|
|
740
823
|
async def update_state(
|
|
741
824
|
self,
|
|
@@ -746,26 +829,50 @@ class ResilientSessionService(BaseSessionService):
|
|
|
746
829
|
scope: str,
|
|
747
830
|
state_delta: dict[str, Any],
|
|
748
831
|
) -> SessionState:
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
live = await self.fallback.update_state(
|
|
752
|
-
agent_id=agent_id,
|
|
753
|
-
user_id=user_id,
|
|
754
|
-
session_id=session_id,
|
|
755
|
-
scope=scope,
|
|
756
|
-
state_delta=state_delta,
|
|
757
|
-
)
|
|
832
|
+
budget = getattr(self.primary, "db_budget", session_db_budget())
|
|
833
|
+
deadline = DatabaseOperationDeadline(budget)
|
|
758
834
|
if session_id:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
835
|
+
lock = self._hydrate_locks.setdefault(session_id, asyncio.Lock())
|
|
836
|
+
await asyncio.wait_for(
|
|
837
|
+
lock.acquire(), timeout=min(budget.lock_seconds, deadline.remaining_seconds()),
|
|
838
|
+
)
|
|
839
|
+
try:
|
|
840
|
+
updated = await deadline.run(self._update_state_locked(
|
|
841
|
+
agent_id=agent_id, user_id=user_id, session_id=session_id,
|
|
842
|
+
scope=scope, state_delta=state_delta,
|
|
843
|
+
))
|
|
844
|
+
self._live_generation[session_id] = self._live_generation.get(session_id, 0) + 1
|
|
845
|
+
return updated
|
|
846
|
+
finally:
|
|
847
|
+
lock.release()
|
|
848
|
+
return await deadline.run(self._update_state_locked(
|
|
849
|
+
agent_id=agent_id, user_id=user_id, session_id=session_id,
|
|
850
|
+
scope=scope, state_delta=state_delta,
|
|
851
|
+
))
|
|
852
|
+
|
|
853
|
+
async def _update_state_locked(
|
|
854
|
+
self, *, agent_id: str, user_id: Optional[str], session_id: Optional[str],
|
|
855
|
+
scope: str, state_delta: dict[str, Any],
|
|
856
|
+
) -> SessionState:
|
|
857
|
+
kwargs = dict(agent_id=agent_id, user_id=user_id, session_id=session_id,
|
|
858
|
+
scope=scope, state_delta=state_delta)
|
|
859
|
+
state_key = (scope, agent_id, user_id or "", session_id or "")
|
|
860
|
+
if session_id not in self._dirty_session_ids and state_key not in self._dirty_state_keys:
|
|
861
|
+
ok, durable = await self._call_primary("update_state", **kwargs)
|
|
862
|
+
if ok:
|
|
863
|
+
await self.fallback.mirror_state_snapshot(cast(SessionState, durable))
|
|
864
|
+
self._hydrated_state_keys.add(state_key)
|
|
865
|
+
return cast(SessionState, durable)
|
|
866
|
+
if state_key not in self._hydrated_state_keys and not (
|
|
867
|
+
scope == "session" and bool(session_id)
|
|
868
|
+
and self._has_complete_live_session(session_id)
|
|
869
|
+
):
|
|
870
|
+
self._clean_read_unavailable()
|
|
871
|
+
self._mark_dirty(session_id)
|
|
872
|
+
self._dirty_state_keys.add(state_key)
|
|
873
|
+
if session_id in self._unsafe_live_session_ids:
|
|
874
|
+
self._clean_read_unavailable()
|
|
875
|
+
return await self.fallback.update_state(**kwargs)
|
|
769
876
|
|
|
770
877
|
async def aclose(self) -> None:
|
|
771
878
|
if self._probe_task is not None and not self._probe_task.done():
|