fred-runtime 3.5.2__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/PKG-INFO +3 -3
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/agent_app.py +119 -29
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/config.py +23 -8
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/context.py +1 -1
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_read_common.py +3 -1
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/mcp.py +5 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/history_display.py +14 -5
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/pod_client.py +8 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/repl.py +10 -5
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/v2_runtime/adapters.py +5 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_prompting.py +11 -8
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_stream_adapter.py +3 -9
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_context.py +2 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/sql_checkpointer.py +35 -19
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/PKG-INFO +3 -3
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/requires.txt +2 -2
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/pyproject.toml +3 -3
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_agent_app.py +168 -1
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_chat_parts_1977.py +1 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_document_reading.py +14 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_client.py +16 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_config_loader.py +80 -1
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_context.py +13 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_history.py +121 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_history_display.py +8 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_hitl_resume_langgraph_integration.py +140 -7
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_openai_compat_router.py +1 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_pod_client.py +9 -3
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_prompting.py +6 -4
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_sql_checkpointer_hitl_claim.py +70 -1
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/README.md +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/__main__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/chat_input_limit.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/container.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/app/platform_sql.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/assembly.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/assets.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/demo.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/demo_migrations/env.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/demo_migrations/versions/d1e2m0a0b0c0_create_demo_echo_notes.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_access/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_access/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_extract/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_extract/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_label_search/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_label_search/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_similarity/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_similarity/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_summarize/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_summarize/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_verbatim/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/document_verbatim/capability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/errors.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/openapi_dump.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/capabilities/registry.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_document_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_tag_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/table_hits.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/deep/deep_runtime.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/inprocess_toolkit_registry.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/kf_vector_search/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/kf_vector_search/toolkit.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/env.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/script.py.mako +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/versions/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/versions/a1e2f3c4d5b6_create_session_history.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/versions/b2f3a4e5c6d7_add_exchange_id.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/migrations/versions/c3d4b5a6f7e8_add_team_and_instance.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/authz.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/checkpoint_hygiene.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/dynamic_prompt.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/frame.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/hitl.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/shared.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/tool_observability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/middleware/tracing_kpi.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_runtime.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/trace_payloads.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/support/thinking.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/SOURCES.txt +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/setup.cfg +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_agent_1973.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_chat_controls_1976.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_document_access_1906.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_document_label_search.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_document_similarity.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_document_summarize.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_endpoints_1974.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_registry_1973.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_routes_tables_1979.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_selection_1974.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_capability_tool_return_convention.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_chat_input_limit.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_checkpoint_namespace.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_context_aware_tool.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_conversational_memory.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_deep_agent_dispatch.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_deep_agent_hitl_integration.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_deep_agent_middleware.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_document_port_error_redaction.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_document_read_scope.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_document_similarity_adapter.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_eval_collector.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_eval_trace.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_fred_workspace_fs.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_graph_capability_bridge.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_graph_checkpoint_namespace.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_graph_runtime_invoke_agent.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_graph_runtime_token_usage_totals.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_kf_base_client_refresh_shapes.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_kf_vector_search_toolkit.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_kf_workspace_client.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_knowledge_search_table_ordering.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_knowledge_search_tool_invoker_sources.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_kpi_display.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_langfuse_tracing.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_langgraph_interrupt_id_semantics.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_mcp_config.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_migrations.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_model_enforcement.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_model_metadata_cache_tokens.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_model_reasoning_enablement.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_model_routing.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_model_routing_provider.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_models_catalog_projection.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_platform_sql.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_platform_sql_postgres_integration.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_checkpoint_namespace.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_loop_regressions_1972.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_message_codec_resume.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_middleware_frame.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_thinking.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_token_usage_totals.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_tool_binding_span_status.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_tool_error_final_2244.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_react_tool_resolution.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_repl_helpers.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_runtime_context_prompt_injection.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_similarity_search_tool_invoker.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_smoke.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_spike_capability_state_1971.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_sql_checkpointer_owner.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_table_repair_wiring.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_team_wiki_adapter.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_token_expiry.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_tool_carrier_middleware_naming.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_tool_loop_trim.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_tool_observability_middleware.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_trace_payloads.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_tracing_kpi_content_confinement.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_url_helpers.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_user_token_refresher.py +0 -0
- {fred_runtime-3.5.2 → fred_runtime-4.0.0}/tests/test_vector_similarity_search_retry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fred-runtime
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
|
|
5
5
|
Author-email: Thales <noreply@thalesgroup.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Requires-Python: <3.13,>=3.12
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: fred-core>=
|
|
16
|
-
Requires-Dist: fred-sdk>=
|
|
15
|
+
Requires-Dist: fred-core>=4.0.0
|
|
16
|
+
Requires-Dist: fred-sdk>=4.0.0
|
|
17
17
|
Requires-Dist: alembic>=1.18.4
|
|
18
18
|
Requires-Dist: deepagents>=0.6.12
|
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -932,6 +932,7 @@ class _AgentExecuteRequest(BaseModel):
|
|
|
932
932
|
context: dict[str, Any] | None = None
|
|
933
933
|
checkpoint_id: str | None = Field(default=None, min_length=1)
|
|
934
934
|
interrupt_id: str | None = Field(default=None, min_length=1)
|
|
935
|
+
occurrence_id: str | None = Field(default=None, min_length=1)
|
|
935
936
|
resume_payload: Any | None = Field(
|
|
936
937
|
default=None,
|
|
937
938
|
description=(
|
|
@@ -956,6 +957,10 @@ class _AgentExecuteRequest(BaseModel):
|
|
|
956
957
|
"TurnOptionsModel; the middleware receives only its own typed slice."
|
|
957
958
|
),
|
|
958
959
|
)
|
|
960
|
+
# Server-derived, never caller-supplied: the occurrences already pending
|
|
961
|
+
# when this resume arrived, stamped by `_authorize_and_resolve` from the
|
|
962
|
+
# admission gate's own checkpoint read. See `_write_turn_history`.
|
|
963
|
+
previously_pending_occurrence_ids: tuple[str, ...] = ()
|
|
959
964
|
|
|
960
965
|
@model_validator(mode="after")
|
|
961
966
|
def _require_message_or_resume(self) -> "_AgentExecuteRequest":
|
|
@@ -1002,6 +1007,7 @@ def _to_internal_request(r: RuntimeExecuteRequest) -> "_AgentExecuteRequest":
|
|
|
1002
1007
|
context=r.to_legacy_context() or None,
|
|
1003
1008
|
checkpoint_id=r.checkpoint_id,
|
|
1004
1009
|
interrupt_id=r.interrupt_id,
|
|
1010
|
+
occurrence_id=r.occurrence_id,
|
|
1005
1011
|
resume_payload=r.resume_payload,
|
|
1006
1012
|
invocation_turns=r.invocation_turns,
|
|
1007
1013
|
inline_tuning=r.inline_tuning,
|
|
@@ -1842,11 +1848,14 @@ async def _authorize_and_resolve(
|
|
|
1842
1848
|
"access_token_expires_at": None, # nosec B105
|
|
1843
1849
|
}
|
|
1844
1850
|
)
|
|
1845
|
-
await _validate_session_checkpoint_access(
|
|
1851
|
+
previously_pending_occurrence_ids = await _validate_session_checkpoint_access(
|
|
1852
|
+
request
|
|
1853
|
+
)
|
|
1846
1854
|
await _enforce_session_ownership(request, authenticated_user, container)
|
|
1847
1855
|
async with runtime_stage_timer(container.get_kpi_writer(), "pod_authz"):
|
|
1848
1856
|
await _authorize_execution_or_raise(request, authenticated_user, container)
|
|
1849
1857
|
internal_req = _to_internal_request(request)
|
|
1858
|
+
internal_req.previously_pending_occurrence_ids = previously_pending_occurrence_ids
|
|
1850
1859
|
# Stamp the trusted service-agent verdict (never the caller-supplied
|
|
1851
1860
|
# context) so per-tool-call re-authorization can mirror the bypass
|
|
1852
1861
|
# `_authorize_execution_or_raise` already granted above (RFC EVAL-AUTH,
|
|
@@ -1923,19 +1932,18 @@ def _validate_resolved_team(
|
|
|
1923
1932
|
_REACT_V2_INTERRUPT_CHANNEL = "__interrupt__"
|
|
1924
1933
|
|
|
1925
1934
|
|
|
1926
|
-
def
|
|
1935
|
+
def _pending_react_v2_interrupt_occurrences(
|
|
1927
1936
|
pending_writes: Sequence[tuple[str, str, Any]],
|
|
1928
|
-
) -> frozenset[str]:
|
|
1937
|
+
) -> frozenset[tuple[str, str | None]]:
|
|
1929
1938
|
"""
|
|
1930
|
-
Extract
|
|
1931
|
-
`"__interrupt__"` write on a ReAct V2 checkpoint (#2216).
|
|
1939
|
+
Extract every pending ReAct V2 `(interrupt_id, occurrence_id)` pair.
|
|
1932
1940
|
|
|
1933
1941
|
Collects ALL matching ids rather than the first one found: FRED's own
|
|
1934
1942
|
`FredHitlMiddleware` never raises more than one interrupt per task
|
|
1935
1943
|
(`test_hitl_resume_two_sequential_prompts_get_different_interrupt_ids`),
|
|
1936
1944
|
but this function does not assume that — it proves the caller's
|
|
1937
|
-
|
|
1938
|
-
|
|
1945
|
+
resume identity matches the exact occurrence, not merely a pending write
|
|
1946
|
+
chosen arbitrarily. Legacy payloads without an occurrence id keep `None`.
|
|
1939
1947
|
|
|
1940
1948
|
Why `pending_writes` is read directly instead of going through
|
|
1941
1949
|
LangGraph's supported `compiled_graph.aget_state(...).tasks[*].interrupts`:
|
|
@@ -1969,7 +1977,7 @@ def _pending_react_v2_interrupt_ids(
|
|
|
1969
1977
|
that don't round-trip through LangGraph's own serde
|
|
1970
1978
|
"""
|
|
1971
1979
|
|
|
1972
|
-
|
|
1980
|
+
occurrences: set[tuple[str, str | None]] = set()
|
|
1973
1981
|
for _task_id, channel, value in pending_writes:
|
|
1974
1982
|
if channel != _REACT_V2_INTERRUPT_CHANNEL:
|
|
1975
1983
|
continue
|
|
@@ -1982,8 +1990,21 @@ def _pending_react_v2_interrupt_ids(
|
|
|
1982
1990
|
if interrupt_id is None and isinstance(candidate, dict):
|
|
1983
1991
|
interrupt_id = candidate.get("id")
|
|
1984
1992
|
if isinstance(interrupt_id, str) and interrupt_id:
|
|
1985
|
-
|
|
1986
|
-
|
|
1993
|
+
payload = getattr(candidate, "value", None)
|
|
1994
|
+
if payload is None and isinstance(candidate, dict):
|
|
1995
|
+
payload = candidate.get("value")
|
|
1996
|
+
occurrence_id = getattr(payload, "occurrence_id", None)
|
|
1997
|
+
if occurrence_id is None and isinstance(payload, dict):
|
|
1998
|
+
occurrence_id = payload.get("occurrence_id")
|
|
1999
|
+
occurrences.add(
|
|
2000
|
+
(
|
|
2001
|
+
interrupt_id,
|
|
2002
|
+
occurrence_id
|
|
2003
|
+
if isinstance(occurrence_id, str) and occurrence_id
|
|
2004
|
+
else None,
|
|
2005
|
+
)
|
|
2006
|
+
)
|
|
2007
|
+
return frozenset(occurrences)
|
|
1987
2008
|
|
|
1988
2009
|
|
|
1989
2010
|
def _resume_checkpoint_namespaces(request: RuntimeExecuteRequest) -> tuple[str, ...]:
|
|
@@ -2020,7 +2041,7 @@ def _resume_checkpoint_namespaces(request: RuntimeExecuteRequest) -> tuple[str,
|
|
|
2020
2041
|
|
|
2021
2042
|
async def _validate_session_checkpoint_access(
|
|
2022
2043
|
request: RuntimeExecuteRequest,
|
|
2023
|
-
) ->
|
|
2044
|
+
) -> tuple[str, ...]:
|
|
2024
2045
|
"""
|
|
2025
2046
|
Validate session/checkpoint consistency for resume-capable runtime requests.
|
|
2026
2047
|
|
|
@@ -2047,6 +2068,11 @@ async def _validate_session_checkpoint_access(
|
|
|
2047
2068
|
- this helper is intentionally conservative: it only validates local
|
|
2048
2069
|
session/checkpoint consistency that the runtime can prove itself
|
|
2049
2070
|
|
|
2071
|
+
Returns the occurrence ids already pending on the resumed checkpoint —
|
|
2072
|
+
the pauses an earlier run has therefore already surfaced and persisted.
|
|
2073
|
+
`_write_turn_history` skips their re-emission so one question keeps one
|
|
2074
|
+
history row. Every non-resume path returns an empty tuple.
|
|
2075
|
+
|
|
2050
2076
|
Example:
|
|
2051
2077
|
- `await _validate_session_checkpoint_access(request)`
|
|
2052
2078
|
|
|
@@ -2066,7 +2092,7 @@ async def _validate_session_checkpoint_access(
|
|
|
2066
2092
|
`request.interrupt_id`, LangGraph's own `Interrupt.id` — this
|
|
2067
2093
|
function requires it to exactly match one of the ids currently
|
|
2068
2094
|
pending on the thread's latest checkpoint
|
|
2069
|
-
(`
|
|
2095
|
+
(`_pending_react_v2_interrupt_occurrences`) — a stale response for an earlier
|
|
2070
2096
|
interrupt must never be accepted for a later one (#2216 P1).
|
|
2071
2097
|
`react_message_codec.py` threads the same id into LangGraph's own
|
|
2072
2098
|
targeted `Command(resume={id: ...})` form as defense in depth, so the
|
|
@@ -2080,7 +2106,7 @@ async def _validate_session_checkpoint_access(
|
|
|
2080
2106
|
or request.resume_payload is not None
|
|
2081
2107
|
)
|
|
2082
2108
|
if not needs_checkpoint_validation:
|
|
2083
|
-
return
|
|
2109
|
+
return ()
|
|
2084
2110
|
|
|
2085
2111
|
session_id = request.effective_session_id()
|
|
2086
2112
|
if not session_id:
|
|
@@ -2092,7 +2118,7 @@ async def _validate_session_checkpoint_access(
|
|
|
2092
2118
|
|
|
2093
2119
|
checkpointer = get_runtime_context().config.checkpointer
|
|
2094
2120
|
if checkpointer is None:
|
|
2095
|
-
return
|
|
2121
|
+
return ()
|
|
2096
2122
|
|
|
2097
2123
|
loaded = None
|
|
2098
2124
|
for checkpoint_ns in _resume_checkpoint_namespaces(request):
|
|
@@ -2142,7 +2168,7 @@ async def _validate_session_checkpoint_access(
|
|
|
2142
2168
|
)
|
|
2143
2169
|
|
|
2144
2170
|
if request.resume_payload is None:
|
|
2145
|
-
return
|
|
2171
|
+
return ()
|
|
2146
2172
|
|
|
2147
2173
|
if channel_values.get("runtime_kind") == "graph_v2":
|
|
2148
2174
|
if channel_values.get("pending") is not True:
|
|
@@ -2161,7 +2187,7 @@ async def _validate_session_checkpoint_access(
|
|
|
2161
2187
|
status_code=status.HTTP_409_CONFLICT,
|
|
2162
2188
|
detail="checkpoint_id does not match the pending checkpoint for this session.",
|
|
2163
2189
|
)
|
|
2164
|
-
return
|
|
2190
|
+
return ()
|
|
2165
2191
|
|
|
2166
2192
|
# Not the legacy Graph runtime (ReAct V2, or the fallback lookup landed on
|
|
2167
2193
|
# some other non-graph_v2 checkpoint kind). #2216 P1: a checkpoint being
|
|
@@ -2169,20 +2195,33 @@ async def _validate_session_checkpoint_access(
|
|
|
2169
2195
|
# an EARLIER interrupt on this same thread must never be allowed to
|
|
2170
2196
|
# resume a LATER one. `request.interrupt_id` must exactly match one of
|
|
2171
2197
|
# the ids currently pending on this checkpoint.
|
|
2172
|
-
|
|
2173
|
-
if not
|
|
2198
|
+
pending_occurrences = _pending_react_v2_interrupt_occurrences(pending_writes)
|
|
2199
|
+
if not pending_occurrences:
|
|
2174
2200
|
raise HTTPException(
|
|
2175
2201
|
status_code=status.HTTP_409_CONFLICT,
|
|
2176
2202
|
detail="checkpoint is not waiting for resume.",
|
|
2177
2203
|
)
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2204
|
+
matching_occurrence_ids = {
|
|
2205
|
+
occurrence_id
|
|
2206
|
+
for interrupt_id, occurrence_id in pending_occurrences
|
|
2207
|
+
if interrupt_id == request.interrupt_id
|
|
2208
|
+
}
|
|
2209
|
+
if not matching_occurrence_ids:
|
|
2182
2210
|
raise HTTPException(
|
|
2183
2211
|
status_code=status.HTTP_409_CONFLICT,
|
|
2184
2212
|
detail="interrupt_id does not match the pending HITL request for this session.",
|
|
2185
2213
|
)
|
|
2214
|
+
if request.occurrence_id not in matching_occurrence_ids:
|
|
2215
|
+
raise HTTPException(
|
|
2216
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
2217
|
+
detail="occurrence_id does not match the pending HITL request for this session.",
|
|
2218
|
+
)
|
|
2219
|
+
|
|
2220
|
+
return tuple(
|
|
2221
|
+
occurrence_id
|
|
2222
|
+
for _, occurrence_id in pending_occurrences
|
|
2223
|
+
if occurrence_id is not None
|
|
2224
|
+
)
|
|
2186
2225
|
|
|
2187
2226
|
|
|
2188
2227
|
@dataclass
|
|
@@ -2217,6 +2256,8 @@ async def _write_turn_history(
|
|
|
2217
2256
|
agent_instance_id: str | None = None,
|
|
2218
2257
|
exchange_id: str | None = None,
|
|
2219
2258
|
resume_payload: Any | None = None,
|
|
2259
|
+
occurrence_id: str | None = None,
|
|
2260
|
+
previously_pending_occurrence_ids: tuple[str, ...] = (),
|
|
2220
2261
|
) -> None:
|
|
2221
2262
|
"""
|
|
2222
2263
|
Persist one agent turn to the history store.
|
|
@@ -2236,6 +2277,9 @@ async def _write_turn_history(
|
|
|
2236
2277
|
- ``payloads`` is the list of ``dict`` produced by ``_iterate_runtime_event_payloads``
|
|
2237
2278
|
- ``resume_payload`` is set for HITL resume turns; the user's choice is stored
|
|
2238
2279
|
as a ``Channel.hitl_response`` row instead of a plain user text row
|
|
2280
|
+
- ``previously_pending_occurrence_ids`` comes from the admission gate
|
|
2281
|
+
(`_validate_session_checkpoint_access`): a resumed run re-raises every
|
|
2282
|
+
sibling pause still waiting, and re-emitting one is not a new question
|
|
2239
2283
|
- silently no-ops when ``session_id`` or ``history_store`` is absent
|
|
2240
2284
|
|
|
2241
2285
|
Event-to-message mapping:
|
|
@@ -2279,16 +2323,33 @@ async def _write_turn_history(
|
|
|
2279
2323
|
|
|
2280
2324
|
# 1. Opening row: user text on normal turns, HITL response on resume turns.
|
|
2281
2325
|
if resume_payload is not None:
|
|
2282
|
-
|
|
2326
|
+
choice_id: str | None = None
|
|
2327
|
+
text: str | None = None
|
|
2283
2328
|
if isinstance(resume_payload, dict):
|
|
2284
|
-
|
|
2329
|
+
raw_choice_id = resume_payload.get("choice_id")
|
|
2330
|
+
if isinstance(raw_choice_id, str) and raw_choice_id:
|
|
2331
|
+
choice_id = raw_choice_id
|
|
2332
|
+
raw_text = resume_payload.get("text")
|
|
2333
|
+
if isinstance(raw_text, str) and raw_text:
|
|
2334
|
+
text = raw_text
|
|
2335
|
+
elif choice_id is None:
|
|
2336
|
+
raw_answer = resume_payload.get("answer")
|
|
2337
|
+
if isinstance(raw_answer, str) and raw_answer:
|
|
2338
|
+
text = raw_answer
|
|
2285
2339
|
elif isinstance(resume_payload, str):
|
|
2286
2340
|
choice_id = resume_payload
|
|
2287
2341
|
else:
|
|
2288
2342
|
choice_id = str(resume_payload)
|
|
2289
|
-
if choice_id:
|
|
2343
|
+
if choice_id or text:
|
|
2290
2344
|
messages.append(
|
|
2291
|
-
make_hitl_response(
|
|
2345
|
+
make_hitl_response(
|
|
2346
|
+
session_id,
|
|
2347
|
+
exchange_id,
|
|
2348
|
+
rank,
|
|
2349
|
+
choice_id=choice_id,
|
|
2350
|
+
text=text,
|
|
2351
|
+
occurrence_id=occurrence_id,
|
|
2352
|
+
)
|
|
2292
2353
|
)
|
|
2293
2354
|
rank += 1
|
|
2294
2355
|
elif request_message:
|
|
@@ -2432,6 +2493,15 @@ async def _write_turn_history(
|
|
|
2432
2493
|
# AND a reload while the gate is still open can reconstruct a
|
|
2433
2494
|
# working (not just readable) prompt.
|
|
2434
2495
|
req = payload.get("request", {})
|
|
2496
|
+
# A resumed run re-raises the siblings still waiting, so the same
|
|
2497
|
+
# pause is emitted again. One question keeps one row: the run that
|
|
2498
|
+
# first surfaced it already wrote it.
|
|
2499
|
+
surfaced_occurrence_id = req.get("occurrence_id")
|
|
2500
|
+
if (
|
|
2501
|
+
surfaced_occurrence_id
|
|
2502
|
+
and surfaced_occurrence_id in previously_pending_occurrence_ids
|
|
2503
|
+
):
|
|
2504
|
+
continue
|
|
2435
2505
|
question = req.get("question") or req.get("title") or "HITL pause"
|
|
2436
2506
|
raw_choices = req.get("choices") or []
|
|
2437
2507
|
raw_pending_calls = req.get("pending_calls") or []
|
|
@@ -2453,6 +2523,7 @@ async def _write_turn_history(
|
|
|
2453
2523
|
title=req.get("title"),
|
|
2454
2524
|
free_text=bool(req.get("free_text")),
|
|
2455
2525
|
interrupt_id=req.get("interrupt_id"),
|
|
2526
|
+
occurrence_id=req.get("occurrence_id"),
|
|
2456
2527
|
checkpoint_id=req.get("checkpoint_id"),
|
|
2457
2528
|
pending_calls=[
|
|
2458
2529
|
{
|
|
@@ -2917,6 +2988,8 @@ async def _stream(
|
|
|
2917
2988
|
agent_instance_id=request.agent_instance_id,
|
|
2918
2989
|
exchange_id=exchange_id,
|
|
2919
2990
|
resume_payload=request.resume_payload,
|
|
2991
|
+
occurrence_id=request.occurrence_id,
|
|
2992
|
+
previously_pending_occurrence_ids=request.previously_pending_occurrence_ids,
|
|
2920
2993
|
)
|
|
2921
2994
|
)
|
|
2922
2995
|
|
|
@@ -3222,6 +3295,7 @@ class _HitlResumeClaim:
|
|
|
3222
3295
|
_thread_id: str
|
|
3223
3296
|
_checkpoint_ns: str
|
|
3224
3297
|
_interrupt_id: str
|
|
3298
|
+
_occurrence_id: str | None
|
|
3225
3299
|
_claim_token: str
|
|
3226
3300
|
|
|
3227
3301
|
async def consume(self) -> None:
|
|
@@ -3229,6 +3303,7 @@ class _HitlResumeClaim:
|
|
|
3229
3303
|
thread_id=self._thread_id,
|
|
3230
3304
|
checkpoint_ns=self._checkpoint_ns,
|
|
3231
3305
|
interrupt_id=self._interrupt_id,
|
|
3306
|
+
occurrence_id=self._occurrence_id,
|
|
3232
3307
|
claim_token=self._claim_token,
|
|
3233
3308
|
)
|
|
3234
3309
|
|
|
@@ -3238,6 +3313,7 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3238
3313
|
session_id: str | None,
|
|
3239
3314
|
checkpoint_ns: str,
|
|
3240
3315
|
interrupt_id: str,
|
|
3316
|
+
occurrence_id: str | None = None,
|
|
3241
3317
|
) -> _HitlResumeClaim | None:
|
|
3242
3318
|
"""
|
|
3243
3319
|
Acquire and confirm the durable single-use HITL resume claim,
|
|
@@ -3296,7 +3372,10 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3296
3372
|
)
|
|
3297
3373
|
return None
|
|
3298
3374
|
claim_token = await checkpointer.aclaim_hitl_resume(
|
|
3299
|
-
thread_id=session_id,
|
|
3375
|
+
thread_id=session_id,
|
|
3376
|
+
checkpoint_ns=checkpoint_ns,
|
|
3377
|
+
interrupt_id=interrupt_id,
|
|
3378
|
+
occurrence_id=occurrence_id,
|
|
3300
3379
|
)
|
|
3301
3380
|
if claim_token is None:
|
|
3302
3381
|
raise RuntimeError("This HITL request is already being resumed.")
|
|
@@ -3305,6 +3384,7 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3305
3384
|
thread_id=session_id,
|
|
3306
3385
|
checkpoint_ns=checkpoint_ns,
|
|
3307
3386
|
interrupt_id=interrupt_id,
|
|
3387
|
+
occurrence_id=occurrence_id,
|
|
3308
3388
|
claim_token=claim_token,
|
|
3309
3389
|
)
|
|
3310
3390
|
except Exception:
|
|
@@ -3317,6 +3397,7 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3317
3397
|
thread_id=session_id,
|
|
3318
3398
|
checkpoint_ns=checkpoint_ns,
|
|
3319
3399
|
interrupt_id=interrupt_id,
|
|
3400
|
+
occurrence_id=occurrence_id,
|
|
3320
3401
|
claim_token=claim_token,
|
|
3321
3402
|
)
|
|
3322
3403
|
raise
|
|
@@ -3330,6 +3411,7 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3330
3411
|
thread_id=session_id,
|
|
3331
3412
|
checkpoint_ns=checkpoint_ns,
|
|
3332
3413
|
interrupt_id=interrupt_id,
|
|
3414
|
+
occurrence_id=occurrence_id,
|
|
3333
3415
|
claim_token=claim_token,
|
|
3334
3416
|
)
|
|
3335
3417
|
raise RuntimeError(
|
|
@@ -3341,6 +3423,7 @@ async def _claim_hitl_resume_before_invocation(
|
|
|
3341
3423
|
_thread_id=session_id,
|
|
3342
3424
|
_checkpoint_ns=checkpoint_ns,
|
|
3343
3425
|
_interrupt_id=interrupt_id,
|
|
3426
|
+
_occurrence_id=occurrence_id,
|
|
3344
3427
|
_claim_token=claim_token,
|
|
3345
3428
|
)
|
|
3346
3429
|
|
|
@@ -3405,6 +3488,7 @@ async def _iterate_runtime_event_payloads(
|
|
|
3405
3488
|
)
|
|
3406
3489
|
resolved_checkpoint_id = request.checkpoint_id or ctx.get("checkpoint_id")
|
|
3407
3490
|
resolved_interrupt_id = request.interrupt_id or ctx.get("interrupt_id")
|
|
3491
|
+
resolved_occurrence_id = request.occurrence_id or ctx.get("occurrence_id")
|
|
3408
3492
|
|
|
3409
3493
|
portable_context = PortableContext(
|
|
3410
3494
|
request_id=request_id,
|
|
@@ -3425,6 +3509,7 @@ async def _iterate_runtime_event_payloads(
|
|
|
3425
3509
|
"template_agent_id": definition.agent_id,
|
|
3426
3510
|
"checkpoint_id": resolved_checkpoint_id,
|
|
3427
3511
|
"interrupt_id": resolved_interrupt_id,
|
|
3512
|
+
"occurrence_id": resolved_occurrence_id,
|
|
3428
3513
|
"execution_action": execution_action,
|
|
3429
3514
|
"exchange_id": exchange_id,
|
|
3430
3515
|
"is_service_agent": ctx.get("is_service_agent"),
|
|
@@ -3663,6 +3748,7 @@ async def _iterate_runtime_event_payloads(
|
|
|
3663
3748
|
# regardless of runtime configuration.
|
|
3664
3749
|
checkpoint_ns="",
|
|
3665
3750
|
interrupt_id=request.interrupt_id,
|
|
3751
|
+
occurrence_id=request.occurrence_id,
|
|
3666
3752
|
)
|
|
3667
3753
|
|
|
3668
3754
|
async for event in executor.stream(react_input, execution_config):
|
|
@@ -4738,6 +4824,8 @@ def _build_agent_router(
|
|
|
4738
4824
|
agent_instance_id=request.agent_instance_id,
|
|
4739
4825
|
exchange_id=exchange_id,
|
|
4740
4826
|
resume_payload=request.resume_payload,
|
|
4827
|
+
occurrence_id=request.occurrence_id,
|
|
4828
|
+
previously_pending_occurrence_ids=internal_req.previously_pending_occurrence_ids,
|
|
4741
4829
|
)
|
|
4742
4830
|
return _terminal_execute_payload(payloads)
|
|
4743
4831
|
|
|
@@ -4825,6 +4913,8 @@ def _build_agent_router(
|
|
|
4825
4913
|
agent_instance_id=request.agent_instance_id,
|
|
4826
4914
|
exchange_id=exchange_id,
|
|
4827
4915
|
resume_payload=request.resume_payload,
|
|
4916
|
+
occurrence_id=request.occurrence_id,
|
|
4917
|
+
previously_pending_occurrence_ids=internal_req.previously_pending_occurrence_ids,
|
|
4828
4918
|
)
|
|
4829
4919
|
return _build_eval_trace(
|
|
4830
4920
|
payloads=payloads,
|
|
@@ -5030,7 +5120,7 @@ def create_agent_app(
|
|
|
5030
5120
|
# 10. start_kpi_tasks — asyncio tasks, after SQL engine is known
|
|
5031
5121
|
# 11. set_runtime_context — wires all built parts into the global config
|
|
5032
5122
|
log_setup(
|
|
5033
|
-
service_name=config.app.
|
|
5123
|
+
service_name=config.app.runtime_id,
|
|
5034
5124
|
log_level=config.app.log_level,
|
|
5035
5125
|
store=build_log_store(
|
|
5036
5126
|
log_store_config=config.storage.log_store,
|
|
@@ -5078,7 +5168,7 @@ def create_agent_app(
|
|
|
5078
5168
|
FredRuntimeContext(
|
|
5079
5169
|
RuntimeConfig(
|
|
5080
5170
|
knowledge_flow_url=config.ai.knowledge_flow_url,
|
|
5081
|
-
service_name=config.app.
|
|
5171
|
+
service_name=config.app.runtime_id,
|
|
5082
5172
|
timeouts=config.ai.timeout,
|
|
5083
5173
|
chat_model_factory=chat_factory,
|
|
5084
5174
|
checkpointer=checkpointer,
|
|
@@ -89,23 +89,36 @@ from ..runtime_context import RuntimeTimeouts
|
|
|
89
89
|
|
|
90
90
|
class PodAppConfig(BaseModel):
|
|
91
91
|
"""
|
|
92
|
-
|
|
92
|
+
Identity and HTTP server settings for an agent pod.
|
|
93
93
|
|
|
94
94
|
Why this exists:
|
|
95
|
-
- every pod needs the same HTTP binding knobs
|
|
96
|
-
|
|
95
|
+
- every pod needs the same identity and HTTP binding knobs, in the same
|
|
96
|
+
place, whoever built it
|
|
97
97
|
- keeping these fields in `app` preserves the familiar startup contract for
|
|
98
98
|
local benches and scrape-based debugging
|
|
99
99
|
|
|
100
100
|
How to use it:
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
|
|
101
|
+
- declare `runtime_id`; every other field defaults sanely for local work
|
|
102
|
+
- the metrics exporter is configured under `observability.kpi.prometheus`,
|
|
103
|
+
not here
|
|
104
104
|
|
|
105
105
|
Example:
|
|
106
|
-
- `PodAppConfig(base_url="/pod/v1", port=8000
|
|
106
|
+
- `PodAppConfig(runtime_id="my-agents", base_url="/pod/v1", port=8000)`
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
|
+
runtime_id: str = Field(
|
|
110
|
+
...,
|
|
111
|
+
pattern=r"^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
|
112
|
+
description=(
|
|
113
|
+
"Slug identifying this pod across every telemetry stream: the KPI "
|
|
114
|
+
"`service` dimension, the `runtime_id` KPI dimension, and the "
|
|
115
|
+
"`service` field of every log record. MUST equal the id the "
|
|
116
|
+
"control-plane registers this pod under in "
|
|
117
|
+
"`runtime_catalog_sources[].runtime_id` — that equality is what "
|
|
118
|
+
"lets a log line be joined to its own KPI. Lowercase slug only, so "
|
|
119
|
+
"no display prose can reach a Prometheus label."
|
|
120
|
+
),
|
|
121
|
+
)
|
|
109
122
|
name: str = "Fred Agent Pod"
|
|
110
123
|
base_url: str = "/api/v1"
|
|
111
124
|
host: str = "127.0.0.1"
|
|
@@ -369,7 +382,9 @@ class AgentPodConfig(BaseModel):
|
|
|
369
382
|
_mcp_configuration: McpConfigurationLike | None = PrivateAttr(default=None)
|
|
370
383
|
_platform_prompt_file: Any | None = PrivateAttr(default=None)
|
|
371
384
|
|
|
372
|
-
|
|
385
|
+
# Required, unlike every other section: `app.runtime_id` has no default, so
|
|
386
|
+
# an omitted `app:` block would leave the pod unable to name itself.
|
|
387
|
+
app: PodAppConfig
|
|
373
388
|
security: SecurityConfiguration
|
|
374
389
|
ai: PodAIConfig = Field(default_factory=PodAIConfig)
|
|
375
390
|
observability: PodObservabilityConfig = Field(
|
|
@@ -142,7 +142,7 @@ class PodApplicationContext:
|
|
|
142
142
|
self._kpi_writer = build_kpi_writer(
|
|
143
143
|
kpi_config=config.observability.kpi,
|
|
144
144
|
opensearch_config=config.storage.opensearch,
|
|
145
|
-
service_name=
|
|
145
|
+
service_name=config.app.runtime_id,
|
|
146
146
|
log_level=config.app.log_level,
|
|
147
147
|
)
|
|
148
148
|
|
|
@@ -196,7 +196,9 @@ def _pagination_footer(result: DocumentMarkdownResult, *, exhaustive: bool) -> s
|
|
|
196
196
|
f"the end is reached.]"
|
|
197
197
|
)
|
|
198
198
|
return (
|
|
199
|
-
f"\n\n[
|
|
199
|
+
f"\n\n[More text remains — {seen} read. You have NOT seen the "
|
|
200
|
+
f"whole document yet; do not present this page as the complete "
|
|
201
|
+
f"document. You may call this tool again with "
|
|
200
202
|
f"offset={result.next_offset} to continue reading.]"
|
|
201
203
|
)
|
|
202
204
|
if result.offset == 0 and result.total_chars == 0:
|
|
@@ -283,6 +283,11 @@ def build_mcp_capability(server: MCPServerConfiguration) -> McpCapability:
|
|
|
283
283
|
manifest = CapabilityManifest(
|
|
284
284
|
id=server.id,
|
|
285
285
|
version=MCP_CAPABILITY_SCHEMA_VERSION,
|
|
286
|
+
# An MCP server has no version of its own: the constant above is the
|
|
287
|
+
# stored-config schema version, and every other capability declares a
|
|
288
|
+
# real semver here. Surfacing the schema constant made every MCP row
|
|
289
|
+
# read "v1" in the admin catalog, which looked like information.
|
|
290
|
+
public_version=None,
|
|
286
291
|
name=server.name,
|
|
287
292
|
description=server.description or server.name,
|
|
288
293
|
icon=_MCP_CAPABILITY_ICON,
|
|
@@ -327,6 +327,7 @@ def run_single_turn(
|
|
|
327
327
|
color_enabled: bool,
|
|
328
328
|
checkpoint_id: str | None = None,
|
|
329
329
|
interrupt_id: str | None = None,
|
|
330
|
+
occurrence_id: str | None = None,
|
|
330
331
|
resume_payload: Any = None,
|
|
331
332
|
inline_tuning: dict[str, Any] | None = None,
|
|
332
333
|
) -> tuple[int, dict[str, Any] | None]:
|
|
@@ -336,12 +337,12 @@ def run_single_turn(
|
|
|
336
337
|
Returns (exit_code, hitl_request) where hitl_request is set when the agent
|
|
337
338
|
is paused at a HITL gate, or None when the turn completed normally.
|
|
338
339
|
|
|
339
|
-
checkpoint_id / interrupt_id
|
|
340
|
+
checkpoint_id / interrupt_id / occurrence_id:
|
|
340
341
|
- forwarded verbatim to the pod, never aliased for each other — the
|
|
341
342
|
legacy Graph V2 runtime resumes via checkpoint_id, ReAct V2 resumes
|
|
342
|
-
via interrupt_id (LangGraph's own `Interrupt.id`).
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
via interrupt_id (LangGraph's own `Interrupt.id`). Tool-raised pauses
|
|
344
|
+
additionally carry occurrence_id. The caller echoes the identifiers
|
|
345
|
+
present on the pending `AwaitingHumanRuntimeEvent.request` verbatim.
|
|
345
346
|
"""
|
|
346
347
|
if not stream:
|
|
347
348
|
payload = client.execute(
|
|
@@ -352,6 +353,7 @@ def run_single_turn(
|
|
|
352
353
|
team_id=team_id,
|
|
353
354
|
checkpoint_id=checkpoint_id,
|
|
354
355
|
interrupt_id=interrupt_id,
|
|
356
|
+
occurrence_id=occurrence_id,
|
|
355
357
|
resume_payload=resume_payload,
|
|
356
358
|
inline_tuning=inline_tuning,
|
|
357
359
|
)
|
|
@@ -390,6 +392,7 @@ def run_single_turn(
|
|
|
390
392
|
team_id=team_id,
|
|
391
393
|
checkpoint_id=checkpoint_id,
|
|
392
394
|
interrupt_id=interrupt_id,
|
|
395
|
+
occurrence_id=occurrence_id,
|
|
393
396
|
resume_payload=resume_payload,
|
|
394
397
|
inline_tuning=inline_tuning,
|
|
395
398
|
):
|
|
@@ -422,7 +425,8 @@ def run_single_turn(
|
|
|
422
425
|
def build_hitl_resume_payload(
|
|
423
426
|
*,
|
|
424
427
|
raw_response: str,
|
|
425
|
-
choices: list[dict[str, Any]] | tuple[dict[str, Any], ...],
|
|
428
|
+
choices: list[dict[str, Any]] | tuple[dict[str, Any], ...] = (),
|
|
429
|
+
free_text: bool = False,
|
|
426
430
|
) -> Any:
|
|
427
431
|
"""
|
|
428
432
|
Convert one terminal HITL answer into the runtime resume payload shape.
|
|
@@ -431,7 +435,12 @@ def build_hitl_resume_payload(
|
|
|
431
435
|
- the interactive shell accepts either a 1-based menu index or a raw
|
|
432
436
|
choice id, but graph `choice_step(...)` expects a structured
|
|
433
437
|
`{"choice_id": ...}` resume payload
|
|
438
|
+
- a typed answer must travel as `{"answer": ...}`, the same field the web
|
|
439
|
+
client sends: a bare string is stored as the chosen `choice_id`, which
|
|
440
|
+
is exactly the audit shape the response contract dropped
|
|
434
441
|
"""
|
|
442
|
+
if free_text:
|
|
443
|
+
return {"answer": raw_response}
|
|
435
444
|
selected_choice_id = raw_response
|
|
436
445
|
if raw_response.isdigit():
|
|
437
446
|
idx = int(raw_response) - 1
|
|
@@ -93,6 +93,7 @@ class AgentPodClient:
|
|
|
93
93
|
agent_instance_id: str | None = None,
|
|
94
94
|
checkpoint_id: str | None = None,
|
|
95
95
|
interrupt_id: str | None = None,
|
|
96
|
+
occurrence_id: str | None = None,
|
|
96
97
|
resume_payload: Any = None,
|
|
97
98
|
inline_tuning: dict[str, Any] | None = None,
|
|
98
99
|
) -> dict[str, Any]:
|
|
@@ -111,6 +112,8 @@ class AgentPodClient:
|
|
|
111
112
|
payload["checkpoint_id"] = checkpoint_id
|
|
112
113
|
if interrupt_id is not None:
|
|
113
114
|
payload["interrupt_id"] = interrupt_id
|
|
115
|
+
if occurrence_id is not None:
|
|
116
|
+
payload["occurrence_id"] = occurrence_id
|
|
114
117
|
if resume_payload is not None:
|
|
115
118
|
payload["resume_payload"] = resume_payload
|
|
116
119
|
if inline_tuning:
|
|
@@ -172,6 +175,7 @@ class AgentPodClient:
|
|
|
172
175
|
agent_instance_id: str | None = None,
|
|
173
176
|
checkpoint_id: str | None = None,
|
|
174
177
|
interrupt_id: str | None = None,
|
|
178
|
+
occurrence_id: str | None = None,
|
|
175
179
|
resume_payload: Any = None,
|
|
176
180
|
inline_tuning: dict[str, Any] | None = None,
|
|
177
181
|
) -> list[dict[str, Any]]:
|
|
@@ -185,6 +189,7 @@ class AgentPodClient:
|
|
|
185
189
|
agent_instance_id=agent_instance_id,
|
|
186
190
|
checkpoint_id=checkpoint_id,
|
|
187
191
|
interrupt_id=interrupt_id,
|
|
192
|
+
occurrence_id=occurrence_id,
|
|
188
193
|
resume_payload=resume_payload,
|
|
189
194
|
inline_tuning=inline_tuning,
|
|
190
195
|
):
|
|
@@ -202,6 +207,7 @@ class AgentPodClient:
|
|
|
202
207
|
agent_instance_id: str | None = None,
|
|
203
208
|
checkpoint_id: str | None = None,
|
|
204
209
|
interrupt_id: str | None = None,
|
|
210
|
+
occurrence_id: str | None = None,
|
|
205
211
|
resume_payload: Any = None,
|
|
206
212
|
inline_tuning: dict[str, Any] | None = None,
|
|
207
213
|
) -> Iterator[dict[str, Any]]:
|
|
@@ -220,6 +226,8 @@ class AgentPodClient:
|
|
|
220
226
|
payload["checkpoint_id"] = checkpoint_id
|
|
221
227
|
if interrupt_id is not None:
|
|
222
228
|
payload["interrupt_id"] = interrupt_id
|
|
229
|
+
if occurrence_id is not None:
|
|
230
|
+
payload["occurrence_id"] = occurrence_id
|
|
223
231
|
if resume_payload is not None:
|
|
224
232
|
payload["resume_payload"] = resume_payload
|
|
225
233
|
if inline_tuning:
|