fred-runtime 2.0.8__tar.gz → 2.0.11__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.
Files changed (113) hide show
  1. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/PKG-INFO +5 -2
  2. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/agent_app.py +15 -0
  3. fred_runtime-2.0.11/fred_runtime/app/container.py +27 -0
  4. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/context.py +14 -0
  5. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/dependencies.py +14 -0
  6. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/mcp_config.py +14 -0
  7. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/__init__.py +14 -0
  8. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/completion.py +14 -0
  9. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/entrypoint.py +14 -0
  10. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/history_display.py +14 -0
  11. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/kpi_display.py +14 -0
  12. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/pod_client.py +14 -0
  13. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/repl.py +14 -0
  14. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/repl_helpers.py +14 -0
  15. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/cli/url_helpers.py +14 -0
  16. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/client.py +14 -0
  17. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/context_aware_tool.py +21 -5
  18. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/token_expiry.py +14 -0
  19. fred_runtime-2.0.11/fred_runtime/deep/__init__.py +15 -0
  20. fred_runtime-2.0.11/fred_runtime/graph/__init__.py +15 -0
  21. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/graph/graph_runtime.py +155 -9
  22. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/integrations/v2_runtime/adapters.py +19 -1
  23. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/model_routing/__init__.py +14 -0
  24. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/model_routing/catalog.py +14 -0
  25. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/model_routing/provider.py +14 -0
  26. fred_runtime-2.0.11/fred_runtime/react/__init__.py +15 -0
  27. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_message_codec.py +14 -0
  28. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_model_adapter.py +14 -0
  29. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tool_rendering.py +14 -0
  30. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tool_utils.py +14 -0
  31. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/checkpoints.py +14 -0
  32. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/sql_checkpointer.py +14 -0
  33. fred_runtime-2.0.11/fred_runtime/support/__init__.py +15 -0
  34. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/support/filesystem_context.py +14 -0
  35. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/support/tool_approval.py +14 -0
  36. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/support/tool_loop.py +14 -0
  37. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/PKG-INFO +5 -2
  38. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/SOURCES.txt +2 -0
  39. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/requires.txt +4 -1
  40. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/pyproject.toml +10 -2
  41. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_agent_app.py +107 -0
  42. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_client.py +14 -0
  43. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_config_loader.py +14 -0
  44. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_context.py +14 -0
  45. fred_runtime-2.0.11/tests/test_context_aware_tool.py +78 -0
  46. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_conversational_memory.py +14 -0
  47. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_eval_collector.py +14 -0
  48. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_eval_trace.py +14 -0
  49. fred_runtime-2.0.11/tests/test_graph_runtime_invoke_agent.py +154 -0
  50. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_graph_runtime_observability.py +14 -0
  51. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_history.py +53 -0
  52. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_kpi_display.py +14 -0
  53. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_mcp_config.py +14 -0
  54. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_model_routing.py +14 -0
  55. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_openai_compat_router.py +14 -0
  56. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_pod_client.py +14 -0
  57. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_repl_helpers.py +14 -0
  58. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_smoke.py +14 -0
  59. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_token_expiry.py +14 -0
  60. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_url_helpers.py +14 -0
  61. fred_runtime-2.0.8/fred_runtime/app/container.py +0 -13
  62. fred_runtime-2.0.8/fred_runtime/deep/__init__.py +0 -1
  63. fred_runtime-2.0.8/fred_runtime/graph/__init__.py +0 -1
  64. fred_runtime-2.0.8/fred_runtime/react/__init__.py +0 -1
  65. fred_runtime-2.0.8/fred_runtime/support/__init__.py +0 -1
  66. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/README.md +0 -0
  67. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/__init__.py +0 -0
  68. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/__init__.py +0 -0
  69. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/_catalogs.py +0 -0
  70. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/config.py +0 -0
  71. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/config_loader.py +0 -0
  72. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/observability_factory.py +0 -0
  73. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/app/openai_compat_router.py +0 -0
  74. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/__init__.py +0 -0
  75. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_base_client.py +0 -0
  76. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_fast_text_client.py +0 -0
  77. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_http_client.py +0 -0
  78. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_logs_client.py +0 -0
  79. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_markdown_media_client.py +0 -0
  80. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
  81. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/kf_workspace_client.py +0 -0
  82. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/mcp_interceptors.py +0 -0
  83. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/mcp_runtime.py +0 -0
  84. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/mcp_toolkit.py +0 -0
  85. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/mcp_utils.py +0 -0
  86. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/structures.py +0 -0
  87. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/common/tool_node_utils.py +0 -0
  88. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/deep/deep_runtime.py +0 -0
  89. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/eval/__init__.py +0 -0
  90. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/eval/collector.py +0 -0
  91. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/integrations/__init__.py +0 -0
  92. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
  93. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/model_routing/contracts.py +0 -0
  94. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/model_routing/resolver.py +0 -0
  95. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_langchain_adapter.py +0 -0
  96. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_prompting.py +0 -0
  97. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_runtime.py +0 -0
  98. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_stream_adapter.py +0 -0
  99. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tool_binding.py +0 -0
  100. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tool_loop.py +0 -0
  101. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tool_resolution.py +0 -0
  102. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/react/react_tracing.py +0 -0
  103. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_context.py +0 -0
  104. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/__init__.py +0 -0
  105. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/model_metadata.py +0 -0
  106. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
  107. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
  108. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/dependency_links.txt +0 -0
  109. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/entry_points.txt +0 -0
  110. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/fred_runtime.egg-info/top_level.txt +0 -0
  111. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/setup.cfg +0 -0
  112. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_kf_workspace_client.py +0 -0
  113. {fred_runtime-2.0.8 → fred_runtime-2.0.11}/tests/test_user_token_refresher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-runtime
3
- Version: 2.0.8
3
+ Version: 2.0.11
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,9 @@ 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>=1.5.3
15
+ Requires-Dist: fred-core>=2.0.5
16
16
  Requires-Dist: fred-sdk>=0.1.11
17
+ Requires-Dist: alembic>=1.18.4
17
18
  Requires-Dist: deepagents>=0.4.11
18
19
  Requires-Dist: httpx>=0.28.1
19
20
  Requires-Dist: langchain-mcp-adapters>=0.2.1
@@ -30,7 +31,9 @@ Requires-Dist: bandit>=1.8.6; extra == "dev"
30
31
  Requires-Dist: basedpyright==1.31.0; extra == "dev"
31
32
  Requires-Dist: detect-secrets>=1.5.0; extra == "dev"
32
33
  Requires-Dist: pytest>=8.4.2; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=1.2.0; extra == "dev"
33
35
  Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
36
+ Requires-Dist: pytest-socket>=0.7.0; extra == "dev"
34
37
  Requires-Dist: ruff>=0.12.5; extra == "dev"
35
38
 
36
39
  # fred-runtime
@@ -563,6 +563,21 @@ class LocalRegistryAgentInvoker(AgentInvokerPort):
563
563
 
564
564
  context_dict = request.context.model_dump(mode="json")
565
565
  context_dict.setdefault("execution_action", ExecutionGrantAction.EXECUTE.value)
566
+ # RFC AGENT-INVOKE: apply the caller's per-call scope onto the callee's
567
+ # retrieval context. These keys are read back when the callee's
568
+ # RuntimeContext is built, so they narrow its document/library/search world.
569
+ # Scope narrows only; the callee still runs under the delegated identity.
570
+ if request.scope is not None:
571
+ if request.scope.document_uids is not None:
572
+ context_dict["selected_document_uids"] = list(
573
+ request.scope.document_uids
574
+ )
575
+ if request.scope.library_ids is not None:
576
+ context_dict["selected_document_libraries_ids"] = list(
577
+ request.scope.library_ids
578
+ )
579
+ if request.scope.search_policy is not None:
580
+ context_dict["search_policy"] = request.scope.search_policy
566
581
  execute_request = _AgentExecuteRequest.model_construct(
567
582
  agent_id=request.agent_id,
568
583
  agent_instance_id=None,
@@ -0,0 +1,27 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Pod container factory — single composition-root entry point."""
16
+
17
+ from __future__ import annotations
18
+
19
+ from fred_runtime.app.config import AgentPodConfig
20
+ from fred_runtime.app.context import PodApplicationContext
21
+
22
+ PodContainer = PodApplicationContext
23
+
24
+
25
+ def build_pod_container(configuration: AgentPodConfig) -> PodContainer:
26
+ """Create a fresh PodApplicationContext with no side effects."""
27
+ return PodApplicationContext(configuration)
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """PodApplicationContext — single composition root for a fred-runtime agent pod."""
2
16
 
3
17
  from __future__ import annotations
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """FastAPI dependency helpers for pod container injection."""
2
16
 
3
17
  from __future__ import annotations
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """Typed representation of the mcp_catalog.yaml loaded by an agent pod."""
2
16
 
3
17
  from __future__ import annotations
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from .completion import completion_candidates
2
16
  from .entrypoint import build_parser, main
3
17
  from .history_display import (
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  from collections.abc import Sequence
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import argparse
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import json
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import re
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import json
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import getpass
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import uuid
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
  import os
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """
2
16
  Compatibility shim — all implementation has moved to fred_runtime.cli.*
3
17
 
@@ -6,6 +6,7 @@ from typing import Any, Callable, Optional
6
6
 
7
7
  import httpx # ← we log/inspect HTTP errors coming from MCP adapters
8
8
  from fred_core.common import OwnerFilter
9
+ from fred_core.common.team_id import is_personal_team_id
9
10
  from fred_core.kpi.kpi_writer_structures import KPIActor
10
11
  from fred_sdk.support.mcp_utils import normalize_mcp_content
11
12
  from langchain_core.tools import BaseTool
@@ -20,6 +21,7 @@ from fred_runtime.runtime_context import get_runtime_context
20
21
  from fred_runtime.runtime_support import (
21
22
  RuntimeContextProvider,
22
23
  get_document_library_tags_ids,
24
+ get_document_uids,
23
25
  get_vector_search_scopes,
24
26
  )
25
27
 
@@ -170,6 +172,15 @@ class ContextAwareTool(BaseTool):
170
172
  library_ids,
171
173
  )
172
174
 
175
+ document_uids = get_document_uids(context)
176
+ if document_uids and "document_uids" in tool_properties:
177
+ kwargs["document_uids"] = document_uids
178
+ logger.info(
179
+ "ContextAwareTool(%s) injecting document filter: %s",
180
+ self.name,
181
+ document_uids,
182
+ )
183
+
173
184
  session_id = context.session_id
174
185
  if (
175
186
  session_id
@@ -183,19 +194,24 @@ class ContextAwareTool(BaseTool):
183
194
  session_id,
184
195
  )
185
196
 
186
- # Force team_id depending on agent settings
187
- if "team_id" in tool_properties and settings.team_id:
188
- kwargs["team_id"] = settings.team_id
197
+ # Force team_id depending on agent settings.
198
+ # Personal-space IDs ("personal-<uuid>") are not real ReBAC teams;
199
+ # don't forward them to tools that look up team membership.
200
+ effective_team_id = (
201
+ settings.team_id if not is_personal_team_id(settings.team_id) else None
202
+ )
203
+ if "team_id" in tool_properties and effective_team_id:
204
+ kwargs["team_id"] = effective_team_id
189
205
  logger.info(
190
206
  "ContextAwareTool(%s) injecting team_id: %s",
191
207
  self.name,
192
- settings.team_id,
208
+ effective_team_id,
193
209
  )
194
210
 
195
211
  # Force owner_filter depending on agent settings
196
212
  if "owner_filter" in tool_properties:
197
213
  owner_filter = (
198
- OwnerFilter.TEAM if settings.team_id else OwnerFilter.PERSONAL
214
+ OwnerFilter.TEAM if effective_team_id else OwnerFilter.PERSONAL
199
215
  )
200
216
  kwargs["owner_filter"] = owner_filter.value
201
217
  logger.info(
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """Shared helpers to detect expired access tokens in HTTP responses/errors."""
2
16
 
3
17
  from __future__ import annotations
@@ -0,0 +1,15 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
@@ -0,0 +1,15 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
@@ -1,3 +1,17 @@
1
+ # Copyright Thales 2026
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """
2
16
  Executable runtime for v2 graph agents.
3
17
 
@@ -22,7 +36,7 @@ import uuid
22
36
  from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
23
37
  from contextlib import asynccontextmanager, nullcontext
24
38
  from dataclasses import dataclass, field
25
- from typing import Protocol, cast
39
+ from typing import Any, Protocol, cast
26
40
 
27
41
  from fred_core.portable import MetricsProvider
28
42
  from fred_sdk.contracts.context import (
@@ -33,6 +47,7 @@ from fred_sdk.contracts.context import (
33
47
  BoundRuntimeContext,
34
48
  ConversationTurn,
35
49
  FetchedResource,
50
+ InvocationScope,
36
51
  PublishedArtifact,
37
52
  ResourceFetchRequest,
38
53
  ResourceScope,
@@ -76,7 +91,7 @@ from langchain_core.messages import BaseMessage
76
91
  from langchain_core.runnables import RunnableConfig
77
92
  from langchain_core.tools import BaseTool
78
93
  from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, empty_checkpoint
79
- from pydantic import BaseModel
94
+ from pydantic import BaseModel, ValidationError
80
95
 
81
96
  from fred_runtime.runtime_support.checkpoints import (
82
97
  AsyncCheckpointReader,
@@ -86,6 +101,84 @@ from fred_runtime.runtime_support.model_metadata import runtime_metadata_from_me
86
101
 
87
102
  logger = logging.getLogger(__name__)
88
103
 
104
+ # RFC AGENT-INVOKE: typed agent invocation — how many times invoke_agent re-asks a
105
+ # callee for a valid JSON object before giving up and returning structured=None.
106
+ _STRUCTURED_OUTPUT_MAX_ATTEMPTS = 2
107
+
108
+
109
+ def _structured_output_instruction(schema: dict[str, Any]) -> str:
110
+ """Instruction appended to the callee message asking for schema-conformant JSON."""
111
+ return (
112
+ "\n\nIMPORTANT: Respond with ONLY a single valid JSON object that conforms to "
113
+ "this JSON Schema. No prose, no explanation, no markdown code fences:\n"
114
+ + json.dumps(schema, ensure_ascii=False)
115
+ )
116
+
117
+
118
+ def _try_json_object(text: str) -> dict[str, Any] | None:
119
+ """Parse ``text`` as JSON, returning it only if it is a JSON object."""
120
+ try:
121
+ parsed = json.loads(text)
122
+ except (ValueError, TypeError):
123
+ return None
124
+ return parsed if isinstance(parsed, dict) else None
125
+
126
+
127
+ def _extract_json_object(text: str) -> dict[str, Any] | None:
128
+ """Best-effort extraction of a single JSON object from an agent's free text.
129
+
130
+ Tries, in order: the whole string, a fenced ``` block, then the first balanced
131
+ ``{...}`` span. Returns ``None`` if nothing parses to an object.
132
+ """
133
+ if not text:
134
+ return None
135
+ candidate = text.strip()
136
+ obj = _try_json_object(candidate)
137
+ if obj is not None:
138
+ return obj
139
+
140
+ fence = candidate.find("```")
141
+ if fence != -1:
142
+ rest = candidate[fence + 3 :]
143
+ if rest[:4].lower() == "json":
144
+ rest = rest[4:]
145
+ end = rest.find("```")
146
+ if end != -1:
147
+ obj = _try_json_object(rest[:end].strip())
148
+ if obj is not None:
149
+ return obj
150
+
151
+ start = candidate.find("{")
152
+ while start != -1:
153
+ depth = 0
154
+ for index in range(start, len(candidate)):
155
+ char = candidate[index]
156
+ if char == "{":
157
+ depth += 1
158
+ elif char == "}":
159
+ depth -= 1
160
+ if depth == 0:
161
+ obj = _try_json_object(candidate[start : index + 1])
162
+ if obj is not None:
163
+ return obj
164
+ break
165
+ start = candidate.find("{", start + 1)
166
+ return None
167
+
168
+
169
+ def _coerce_structured_payload(
170
+ content: str, output_schema: type[BaseModel]
171
+ ) -> dict[str, Any] | None:
172
+ """Extract + validate a callee's text into ``output_schema``; ``None`` on failure."""
173
+ parsed = _extract_json_object(content)
174
+ if parsed is None:
175
+ return None
176
+ try:
177
+ return output_schema.model_validate(parsed).model_dump(mode="json")
178
+ except ValidationError:
179
+ return None
180
+
181
+
89
182
  GraphNodeHandler = Callable[
90
183
  [BaseModel, GraphNodeContext], GraphNodeResult | Awaitable[GraphNodeResult]
91
184
  ]
@@ -669,7 +762,16 @@ class _GraphNodeExecutionContext:
669
762
  message: str,
670
763
  *,
671
764
  prior_turns: tuple[ConversationTurn, ...] = (),
765
+ output_schema: type[BaseModel] | None = None,
766
+ scope: InvocationScope | None = None,
672
767
  ) -> AgentInvocationResult:
768
+ """Invoke another registered agent for one turn (RFC AGENT-INVOKE).
769
+
770
+ When ``output_schema`` is given the callee is asked for a JSON object of that
771
+ shape; the validated payload is attached to ``result.structured`` (with a
772
+ bounded retry, ``None`` if it could not be coerced). When ``scope`` is given,
773
+ the callee's retrieval world is narrowed for this call only.
774
+ """
673
775
  agent_invoker = self.services.agent_invoker
674
776
  if agent_invoker is None:
675
777
  raise RuntimeError(
@@ -680,6 +782,13 @@ class _GraphNodeExecutionContext:
680
782
 
681
783
  self.emit_status("invoke_agent", detail=agent_id)
682
784
 
785
+ schema_dict = (
786
+ output_schema.model_json_schema() if output_schema is not None else None
787
+ )
788
+ max_attempts = (
789
+ _STRUCTURED_OUTPUT_MAX_ATTEMPTS if output_schema is not None else 1
790
+ )
791
+
683
792
  span = _start_runtime_span(
684
793
  services=self.services,
685
794
  binding=self.binding,
@@ -690,6 +799,7 @@ class _GraphNodeExecutionContext:
690
799
  "target_agent_id": agent_id,
691
800
  },
692
801
  )
802
+ result: AgentInvocationResult | None = None
693
803
  try:
694
804
  with _graph_phase_timer(
695
805
  metrics=self.services.metrics,
@@ -702,14 +812,49 @@ class _GraphNodeExecutionContext:
702
812
  "target_agent_id": agent_id,
703
813
  },
704
814
  ) as kpi_dims:
705
- result = await agent_invoker.invoke(
706
- AgentInvocationRequest(
707
- agent_id=agent_id,
708
- message=message,
709
- context=self.binding.portable_context,
710
- prior_turns=prior_turns,
815
+ for attempt in range(max_attempts):
816
+ effective_message = message
817
+ if schema_dict is not None:
818
+ effective_message = message + _structured_output_instruction(
819
+ schema_dict
820
+ )
821
+ if attempt > 0:
822
+ effective_message = (
823
+ "Your previous response was not a valid JSON object. "
824
+ + effective_message
825
+ )
826
+ result = await agent_invoker.invoke(
827
+ AgentInvocationRequest(
828
+ agent_id=agent_id,
829
+ message=effective_message,
830
+ context=self.binding.portable_context,
831
+ prior_turns=prior_turns,
832
+ scope=scope,
833
+ output_schema=schema_dict,
834
+ )
835
+ )
836
+ if output_schema is None or result.is_error:
837
+ break
838
+ structured = _coerce_structured_payload(
839
+ result.content, output_schema
840
+ )
841
+ if structured is not None:
842
+ result = result.model_copy(update={"structured": structured})
843
+ break
844
+
845
+ assert result is not None
846
+ if (
847
+ output_schema is not None
848
+ and not result.is_error
849
+ and result.structured is None
850
+ ):
851
+ logger.warning(
852
+ "invoke_agent(%s): could not coerce output to %s after %d "
853
+ "attempt(s); returning structured=None",
854
+ agent_id,
855
+ output_schema.__name__,
856
+ max_attempts,
711
857
  )
712
- )
713
858
  if result.is_error:
714
859
  kpi_dims["status"] = "error"
715
860
  if span is not None:
@@ -723,6 +868,7 @@ class _GraphNodeExecutionContext:
723
868
  finally:
724
869
  if span is not None:
725
870
  span.end()
871
+ assert result is not None
726
872
  return result
727
873
 
728
874
  async def publish_text(