fred-runtime 3.3.0__tar.gz → 3.3.1__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 (119) hide show
  1. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/PKG-INFO +4 -4
  2. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/agent_app.py +70 -3
  3. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_runtime.py +1 -1
  4. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/deep/deep_runtime.py +12 -6
  5. fred_runtime-3.3.1/fred_runtime/integrations/inprocess_toolkit_registry.py +50 -0
  6. fred_runtime-3.3.1/fred_runtime/integrations/kf_vector_search/__init__.py +21 -0
  7. fred_runtime-3.3.1/fred_runtime/integrations/kf_vector_search/toolkit.py +186 -0
  8. fred_runtime-3.3.1/fred_runtime/react/react_prompting.py +310 -0
  9. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_runtime.py +7 -5
  10. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_context.py +3 -2
  11. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/sql_checkpointer.py +219 -1
  12. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/PKG-INFO +4 -4
  13. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/SOURCES.txt +7 -0
  14. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/requires.txt +3 -3
  15. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/pyproject.toml +4 -4
  16. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_agent_app.py +131 -18
  17. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_client.py +0 -1
  18. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_config_loader.py +0 -1
  19. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_context.py +0 -1
  20. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_context_aware_tool.py +1 -2
  21. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_conversational_memory.py +5 -10
  22. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_eval_trace.py +1 -2
  23. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_fred_workspace_fs.py +2 -3
  24. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_graph_runtime_invoke_agent.py +5 -6
  25. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_graph_runtime_observability.py +1 -2
  26. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_history.py +5 -6
  27. fred_runtime-3.3.1/tests/test_kf_vector_search_toolkit.py +136 -0
  28. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_kf_workspace_client.py +0 -1
  29. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_mcp_config.py +0 -1
  30. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_model_routing.py +0 -1
  31. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_openai_compat_router.py +2 -3
  32. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_pod_client.py +0 -1
  33. fred_runtime-3.3.1/tests/test_react_prompting.py +216 -0
  34. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_react_thinking.py +12 -13
  35. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_repl_helpers.py +0 -1
  36. fred_runtime-3.3.1/tests/test_runtime_context_prompt_injection.py +164 -0
  37. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_smoke.py +5 -6
  38. fred_runtime-3.3.1/tests/test_sql_checkpointer_owner.py +271 -0
  39. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_token_expiry.py +0 -1
  40. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_user_token_refresher.py +0 -1
  41. fred_runtime-3.3.0/fred_runtime/react/react_prompting.py +0 -164
  42. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/README.md +0 -0
  43. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/__init__.py +0 -0
  44. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/__init__.py +0 -0
  45. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/_catalogs.py +0 -0
  46. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/config.py +0 -0
  47. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/config_loader.py +0 -0
  48. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/container.py +0 -0
  49. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/context.py +0 -0
  50. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/dependencies.py +0 -0
  51. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/mcp_config.py +0 -0
  52. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/observability_factory.py +0 -0
  53. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/openai_compat_router.py +0 -0
  54. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/__init__.py +0 -0
  55. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/completion.py +0 -0
  56. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/entrypoint.py +0 -0
  57. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/history_display.py +0 -0
  58. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/kpi_display.py +0 -0
  59. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/pod_client.py +0 -0
  60. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/repl.py +0 -0
  61. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/repl_helpers.py +0 -0
  62. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/url_helpers.py +0 -0
  63. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/client.py +0 -0
  64. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/__init__.py +0 -0
  65. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/context_aware_tool.py +0 -0
  66. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_base_client.py +0 -0
  67. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_fast_text_client.py +0 -0
  68. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_http_client.py +0 -0
  69. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_logs_client.py +0 -0
  70. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_markdown_media_client.py +0 -0
  71. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
  72. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_workspace_client.py +0 -0
  73. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_interceptors.py +0 -0
  74. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_toolkit.py +0 -0
  75. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_utils.py +0 -0
  76. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/structures.py +0 -0
  77. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/token_expiry.py +0 -0
  78. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/tool_node_utils.py +0 -0
  79. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/deep/__init__.py +0 -0
  80. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/eval/__init__.py +0 -0
  81. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/eval/collector.py +0 -0
  82. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/graph/__init__.py +0 -0
  83. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/graph/graph_runtime.py +0 -0
  84. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/__init__.py +0 -0
  85. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
  86. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/v2_runtime/adapters.py +0 -0
  87. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/__init__.py +0 -0
  88. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/catalog.py +0 -0
  89. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/contracts.py +0 -0
  90. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/provider.py +0 -0
  91. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/resolver.py +0 -0
  92. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/__init__.py +0 -0
  93. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_langchain_adapter.py +0 -0
  94. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_message_codec.py +0 -0
  95. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_model_adapter.py +0 -0
  96. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_stream_adapter.py +0 -0
  97. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_binding.py +0 -0
  98. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_loop.py +0 -0
  99. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_rendering.py +0 -0
  100. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_resolution.py +0 -0
  101. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_utils.py +0 -0
  102. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tracing.py +0 -0
  103. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/__init__.py +0 -0
  104. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/checkpoints.py +0 -0
  105. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/model_metadata.py +0 -0
  106. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
  107. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
  108. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/__init__.py +0 -0
  109. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/filesystem_context.py +0 -0
  110. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/thinking.py +0 -0
  111. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/tool_approval.py +0 -0
  112. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/tool_loop.py +0 -0
  113. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/dependency_links.txt +0 -0
  114. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/entry_points.txt +0 -0
  115. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/top_level.txt +0 -0
  116. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/setup.cfg +0 -0
  117. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_eval_collector.py +0 -0
  118. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_kpi_display.py +0 -0
  119. {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_url_helpers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-runtime
3
- Version: 3.3.0
3
+ Version: 3.3.1
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>=3.4.0
16
- Requires-Dist: fred-sdk>=3.3.0
15
+ Requires-Dist: fred-core>=3.4.1
16
+ Requires-Dist: fred-sdk>=3.3.1
17
17
  Requires-Dist: alembic>=1.18.4
18
18
  Requires-Dist: deepagents>=0.4.11
19
19
  Requires-Dist: httpx>=0.28.1
@@ -25,7 +25,7 @@ Requires-Dist: langfuse>=3.0.0
25
25
  Requires-Dist: requests<3,>=2.32
26
26
  Requires-Dist: PyYAML>=6.0
27
27
  Provides-Extra: app
28
- Requires-Dist: fastapi>=0.116; extra == "app"
28
+ Requires-Dist: fastapi>=0.139.0; extra == "app"
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: bandit>=1.8.6; extra == "dev"
31
31
  Requires-Dist: basedpyright==1.31.0; extra == "dev"
@@ -63,9 +63,13 @@ from fred_core.logs.log_setup import log_setup
63
63
  from fred_core.logs.memory_log_store import RamLogStore
64
64
  from fred_core.security.models import AuthorizationError
65
65
  from fred_core.security.oidc import get_keycloak_client_id, get_keycloak_url
66
- from fred_core.security.rebac.rebac_engine import TeamPermission
66
+ from fred_core.security.rebac.rebac_engine import (
67
+ ORGANIZATION_ID,
68
+ OrganizationPermission,
69
+ TeamPermission,
70
+ )
67
71
  from fred_core.security.rebac.rebac_factory import rebac_factory
68
- from fred_core.security.structure import KeycloakUser
72
+ from fred_core.security.structure import KeycloakUser, is_service_agent
69
73
  from fred_sdk.contracts.context import (
70
74
  AgentInvocationRequest,
71
75
  AgentInvocationResult,
@@ -110,6 +114,7 @@ from fred_runtime.react.react_runtime import ReActRuntime
110
114
  from fred_runtime.runtime_support.checkpoints import load_checkpoint
111
115
 
112
116
  from ..common.structures import AgentSettingsLike
117
+ from ..integrations.inprocess_toolkit_registry import build_inprocess_toolkit
113
118
  from ..integrations.v2_runtime.adapters import (
114
119
  CompositeToolInvoker,
115
120
  FredKnowledgeSearchToolInvoker,
@@ -1183,6 +1188,20 @@ async def _authorize_execution_or_raise(
1183
1188
  "(runtime_context.team_id)"
1184
1189
  ),
1185
1190
  )
1191
+ if is_service_agent(authenticated_user):
1192
+ # Solution A (RFC EVAL-AUTH): a service identity (the evaluation worker) is
1193
+ # recognized for execution, scoped to the request team_id, without any stored
1194
+ # OpenFGA relation. Legitimacy is anchored upstream at campaign creation. The
1195
+ # team_id is guaranteed non-None above, so this stays team-scoped (never global).
1196
+ _emit_audit_event(
1197
+ container,
1198
+ "info",
1199
+ "service_agent_authorized",
1200
+ user_id=authenticated_user.uid,
1201
+ team_id=team_id,
1202
+ agent_instance_id=request.agent_instance_id,
1203
+ )
1204
+ return
1186
1205
  try:
1187
1206
  await rebac.check_user_team_permission_or_raise(
1188
1207
  authenticated_user, TeamPermission.CAN_READ, team_id
@@ -1255,6 +1274,31 @@ async def _enforce_session_ownership(
1255
1274
  )
1256
1275
 
1257
1276
 
1277
+ async def _caller_can_manage_platform(caller: KeycloakUser | None) -> bool:
1278
+ """True when the caller holds org-level ``can_manage_platform`` (CTRLP-12 C1).
1279
+
1280
+ The delete endpoints use this as an **admin branch**: a platform service
1281
+ principal — e.g. the control-plane lifecycle worker erasing a conversation at
1282
+ window expiry — may delete a session it does not personally own. Only the
1283
+ per-user *ownership* check is waived; *authentication* stays enforced upstream
1284
+ by ``_authenticated_user``.
1285
+
1286
+ Fails closed: returns False when there is no caller or when ReBAC is disabled
1287
+ (dev/no-security), so ownership behaviour is unchanged there and the bypass
1288
+ activates only when ReBAC is actually enforcing and the org permission is
1289
+ granted. Reuses the AUTHZ-01 ``can_manage_platform`` permission — no second
1290
+ bypass is forked.
1291
+ """
1292
+ if caller is None:
1293
+ return False
1294
+ rebac = get_runtime_context().config.rebac_engine
1295
+ if rebac is None or not rebac.enabled:
1296
+ return False
1297
+ return await rebac.has_user_permission(
1298
+ caller, OrganizationPermission.CAN_MANAGE_PLATFORM, ORGANIZATION_ID
1299
+ )
1300
+
1301
+
1258
1302
  async def _authorize_and_resolve(
1259
1303
  request: RuntimeExecuteRequest,
1260
1304
  *,
@@ -2036,6 +2080,10 @@ async def _iterate_runtime_event_payloads(
2036
2080
  # at prepare-execution and the frontend forwards it — but it was also
2037
2081
  # silently dropped here, so no agent ever received a selected prompt.
2038
2082
  context_prompt_text=ctx.get("context_prompt_text"),
2083
+ # Rebuilt by the frontend from the attachments that currently exist.
2084
+ # When the final file is deleted this is absent, so the per-turn runtime
2085
+ # notice disappears without leaving a checkpointed system message behind.
2086
+ attachments_markdown=ctx.get("attachments_markdown"),
2039
2087
  )
2040
2088
 
2041
2089
  binding = BoundRuntimeContext(
@@ -2195,6 +2243,7 @@ def _build_agent_router(
2195
2243
  async def get_kpi_turns(
2196
2244
  limit: int = 50,
2197
2245
  container: PodApplicationContext = Depends(get_pod_container),
2246
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2198
2247
  ) -> list[KpiTurnRecord]:
2199
2248
  """
2200
2249
  Return recent agent.turn_completed KPI events, newest first.
@@ -2210,6 +2259,11 @@ def _build_agent_router(
2210
2259
  Grafana or Prometheus; this exposes the pod-local ring buffer
2211
2260
  - max 200 entries retained in memory (oldest evicted automatically)
2212
2261
  """
2262
+ rebac = get_runtime_context().config.rebac_engine
2263
+ if caller is not None and rebac is not None and rebac.enabled:
2264
+ await rebac.check_user_permission_or_raise(
2265
+ caller, OrganizationPermission.CAN_READ_METRICS, ORGANIZATION_ID
2266
+ )
2213
2267
  with container._kpi_turns_lock:
2214
2268
  events = list(container.kpi_turns_buffer)
2215
2269
  events.reverse()
@@ -2219,6 +2273,7 @@ def _build_agent_router(
2219
2273
  async def get_audit_events(
2220
2274
  limit: int = 50,
2221
2275
  container: PodApplicationContext = Depends(get_pod_container),
2276
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2222
2277
  ) -> list[AuditEventRecord]:
2223
2278
  """
2224
2279
  Return recent security audit events, newest first.
@@ -2233,6 +2288,11 @@ def _build_agent_router(
2233
2288
  this ring buffer so the CLI can query them directly
2234
2289
  - max 200 entries retained in memory
2235
2290
  """
2291
+ rebac = get_runtime_context().config.rebac_engine
2292
+ if caller is not None and rebac is not None and rebac.enabled:
2293
+ await rebac.check_user_permission_or_raise(
2294
+ caller, OrganizationPermission.CAN_MANAGE_PLATFORM, ORGANIZATION_ID
2295
+ )
2236
2296
  with container._audit_events_lock:
2237
2297
  events = list(container.audit_events_buffer)
2238
2298
  events.reverse()
@@ -2392,6 +2452,11 @@ def _build_agent_router(
2392
2452
  detail="No history store configured — session history is unavailable.",
2393
2453
  )
2394
2454
  caller_uid = caller.uid if caller is not None else None
2455
+ if await _caller_can_manage_platform(caller):
2456
+ # Platform admin (e.g. CTRLP-12 lifecycle erase-at-expiry): delete every
2457
+ # row for the session, not just the caller's — ownership is waived,
2458
+ # authentication stays enforced upstream.
2459
+ caller_uid = None
2395
2460
  count = await history_store.delete_session(
2396
2461
  session_id=session_id, user_id=caller_uid
2397
2462
  )
@@ -2717,7 +2782,8 @@ def _build_agent_router(
2717
2782
  caller_uid = caller.uid if caller is not None else None
2718
2783
  history_store = _get_history_store_for_owned_access(caller)
2719
2784
  if (
2720
- caller_uid is not None
2785
+ not await _caller_can_manage_platform(caller)
2786
+ and caller_uid is not None
2721
2787
  and history_store is not None
2722
2788
  and not await history_store.session_belongs_to_user(session_id, caller_uid)
2723
2789
  ):
@@ -3073,6 +3139,7 @@ def create_agent_app(
3073
3139
  checkpointer=checkpointer,
3074
3140
  history_store=history_store,
3075
3141
  mcp_configuration=config.get_mcp_configuration(),
3142
+ inprocess_toolkit_factory=build_inprocess_toolkit,
3076
3143
  control_plane_url=config.platform.control_plane_url,
3077
3144
  rebac_engine=rebac_engine,
3078
3145
  security_profile=(
@@ -375,7 +375,7 @@ class MCPRuntime:
375
375
  )
376
376
  return
377
377
  for server in self.inprocess_servers:
378
- toolkit = factory(server.provider)
378
+ toolkit = factory(server.provider, self.agent_instance)
379
379
  if toolkit is None:
380
380
  logger.warning(
381
381
  "[MCP] agent=%s no toolkit built for provider=%s (server=%s)",
@@ -38,7 +38,7 @@ from langchain_core.tools import BaseTool
38
38
  from langgraph.types import Checkpointer
39
39
 
40
40
  from fred_runtime.react.react_prompting import (
41
- build_guardrail_suffix as _build_guardrail_suffix,
41
+ compose_system_prompt as _compose_system_prompt,
42
42
  )
43
43
  from fred_runtime.react.react_prompting import (
44
44
  render_prompt_template as _render_prompt_template,
@@ -117,11 +117,17 @@ class DeepAgentRuntime(ReActRuntime):
117
117
  binding=binding,
118
118
  agent_id=self.definition.agent_id,
119
119
  )
120
- system_prompt = (
121
- f"{system_prompt}"
122
- f"{_build_runtime_tool_prompt_suffix(bound_tools)}"
123
- f"{_build_guardrail_suffix(self.definition)}"
124
- f"{_filesystem_prompt_suffix(filesystem_tools_enabled=filesystem_tools_enabled)}"
120
+ system_prompt = _compose_system_prompt(
121
+ system_prompt,
122
+ binding=binding,
123
+ definition=self.definition,
124
+ agent_id=self.definition.agent_id,
125
+ tool_suffix=_build_runtime_tool_prompt_suffix(bound_tools),
126
+ runtime_suffixes=(
127
+ _filesystem_prompt_suffix(
128
+ filesystem_tools_enabled=filesystem_tools_enabled
129
+ ),
130
+ ),
125
131
  )
126
132
  compiled_agent = _create_compiled_deep_agent(
127
133
  model=self._model,
@@ -0,0 +1,50 @@
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
+ Registry mapping catalog `provider` keys to in-process MCP toolkits (MIGR-03.03).
16
+
17
+ Why this module exists:
18
+ - an MCP catalog server with `transport: inprocess` names a `provider`; the MCP
19
+ runtime asks this factory to build the matching toolkit for the current agent turn
20
+ - keeping the mapping here (not in `agent_app`) lets the runtime wire a single
21
+ factory reference at startup without hardcoding provider knowledge in the boot path
22
+
23
+ How to use it:
24
+ - pass `build_inprocess_toolkit` as `RuntimeConfig.inprocess_toolkit_factory`
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ from typing import Any
31
+
32
+ from fred_runtime.integrations.kf_vector_search import (
33
+ KF_VECTOR_SEARCH_PROVIDER,
34
+ KfVectorSearchToolkit,
35
+ )
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ def build_inprocess_toolkit(provider: str | None, agent: Any) -> Any | None:
41
+ """
42
+ Build the in-process toolkit for `provider`, bound to the current agent turn.
43
+
44
+ Returns None for an unknown or missing provider so the MCP runtime skips it with
45
+ a warning rather than failing the whole agent.
46
+ """
47
+ if provider == KF_VECTOR_SEARCH_PROVIDER:
48
+ return KfVectorSearchToolkit(agent=agent)
49
+ logger.warning("[MCP] unknown inprocess provider=%s; no toolkit built.", provider)
50
+ return None
@@ -0,0 +1,21 @@
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
+ """In-process Knowledge Flow vector-search MCP toolkit."""
15
+
16
+ from fred_runtime.integrations.kf_vector_search.toolkit import (
17
+ KF_VECTOR_SEARCH_PROVIDER,
18
+ KfVectorSearchToolkit,
19
+ )
20
+
21
+ __all__ = ["KF_VECTOR_SEARCH_PROVIDER", "KfVectorSearchToolkit"]
@@ -0,0 +1,186 @@
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
+ In-process Knowledge Flow vector-search toolkit for v2 ReAct agents (MIGR-03.03).
16
+
17
+ Why this module exists:
18
+ - restores the kea behaviour where the "search_documents" tool, selected from the
19
+ Tools tab as an MCP server, returns typed `VectorSearchHit` sources so the chat
20
+ UI renders the Sources panel (and clickable `[N]` citations)
21
+ - in kea that tool was an *inprocess* provider (`transport: inprocess`,
22
+ `provider: kf_vector_search`) — NOT a real remote MCP endpoint. A remote MCP tool
23
+ returns plain text with no typed artifact, so the panel cannot render. This
24
+ toolkit re-establishes the inprocess path on swift.
25
+
26
+ Key design:
27
+ - exposes one LangChain tool (`search_documents_using_vectorization`) that returns a
28
+ `ToolInvocationResult` directly (NOT a `content_and_artifact` tuple). The shared
29
+ runtime-provider resolver invokes provider tools with a plain args dict, which
30
+ collapses a `content_and_artifact` tuple to its content string and drops the
31
+ artifact — so the sources must ride on a returned `ToolInvocationResult`, which the
32
+ resolver extracts (see `react_tool_resolution._resolve_runtime_provider_tool`).
33
+ `blocks` carry the hit JSON for the LLM; `sources` carry the typed hits the chat
34
+ Sources panel renders. This mirrors the built-in `knowledge.search` tool ref.
35
+ - retrieval scoping (libraries, document uids, search policy, session/corpus scope,
36
+ team) is read from the bound runtime context at call time — identical to the
37
+ built-in `knowledge.search` tool ref used by Rico, so both paths behave the same
38
+
39
+ How to use it:
40
+ - built by `build_inprocess_toolkit` when a catalog server declares
41
+ `provider: "kf_vector_search"`; the MCP runtime calls `.tools()` to collect the
42
+ LangChain tools
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import logging
48
+ from typing import Sequence
49
+
50
+ from fred_core.common import OwnerFilter
51
+ from fred_core.common.team_id import is_personal_team_id
52
+ from fred_core.store.vector_search import VectorSearchHit
53
+ from fred_sdk.contracts.context import (
54
+ ToolContentBlock,
55
+ ToolContentKind,
56
+ ToolInvocationResult,
57
+ )
58
+ from langchain_core.tools import BaseTool, tool
59
+
60
+ from fred_runtime.common.kf_base_client import KnowledgeFlowAgentContext
61
+ from fred_runtime.common.kf_vectorsearch_client import VectorSearchClient
62
+ from fred_runtime.runtime_support.request_context_helpers import (
63
+ get_document_library_tags_ids,
64
+ get_document_uids,
65
+ get_rag_knowledge_scope,
66
+ get_search_policy,
67
+ get_vector_search_scopes,
68
+ )
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+ KF_VECTOR_SEARCH_PROVIDER = "kf_vector_search"
73
+
74
+ # Only the fields the LLM needs for citation and reasoning are exposed. URL and
75
+ # operational fields are excluded so the model cannot reproduce broken or internal
76
+ # paths in its reply — this mirrors `_invoke_knowledge_search` (the tool-ref path).
77
+ _LLM_FIELDS = {"uid", "title", "content", "file_name", "page", "section", "score"}
78
+
79
+
80
+ def _llm_slice(hit: VectorSearchHit) -> dict[str, object]:
81
+ return {k: v for k, v in hit.model_dump(mode="json").items() if k in _LLM_FIELDS}
82
+
83
+
84
+ class KfVectorSearchToolkit:
85
+ """
86
+ Inprocess toolkit that binds a Knowledge Flow vector search to one agent turn.
87
+
88
+ Why this class exists:
89
+ - the MCP runtime resolves inprocess providers to a toolkit object and calls its
90
+ `tools()` method to collect LangChain tools
91
+ - the toolkit is built per request from the agent shim (which carries the bound
92
+ runtime context and agent settings), so each tool call reads the current
93
+ session scope
94
+
95
+ How to use it:
96
+ - `KfVectorSearchToolkit(agent=shim).tools()`
97
+ """
98
+
99
+ def __init__(self, *, agent: KnowledgeFlowAgentContext) -> None:
100
+ self._agent = agent
101
+ self._client = VectorSearchClient(agent=agent)
102
+
103
+ def tools(self) -> list[BaseTool]:
104
+ agent = self._agent
105
+ client = self._client
106
+
107
+ @tool("search_documents_using_vectorization")
108
+ async def search_documents_using_vectorization(
109
+ question: str,
110
+ top_k: int = 8,
111
+ ) -> ToolInvocationResult:
112
+ """Search the selected document libraries using semantic similarity (RAG).
113
+
114
+ Call this tool BEFORE answering any factual, technical, or domain-specific
115
+ question — the corpus may hold more specific or recent information than you
116
+ already know. Skip it only for purely conversational exchanges (greetings,
117
+ thanks, clarifying what was just said).
118
+
119
+ Returns ranked hits with title and content. Cite each grounded claim with
120
+ the bracketed rank of the hit it relies on: [1], [2]. Only use information
121
+ actually present in the returned hits; never invent facts beyond them.
122
+ """
123
+ runtime_context = agent.runtime_context
124
+
125
+ if get_rag_knowledge_scope(runtime_context) == "general_only":
126
+ return ToolInvocationResult(
127
+ tool_ref=KF_VECTOR_SEARCH_PROVIDER,
128
+ blocks=(
129
+ ToolContentBlock(
130
+ kind=ToolContentKind.JSON,
131
+ data={
132
+ "sources": [],
133
+ "note": "Corpus retrieval skipped in general-only mode.",
134
+ },
135
+ ),
136
+ ),
137
+ sources=(),
138
+ )
139
+
140
+ top_k = top_k if isinstance(top_k, int) and top_k > 0 else 8
141
+ team_id = agent.agent_settings.team_id
142
+ scoped_team = bool(team_id) and not is_personal_team_id(team_id)
143
+ include_session_scope, include_corpus_scope = get_vector_search_scopes(
144
+ runtime_context
145
+ )
146
+
147
+ hits: Sequence[VectorSearchHit] = await client.search(
148
+ question=question,
149
+ top_k=top_k,
150
+ document_library_tags_ids=get_document_library_tags_ids(
151
+ runtime_context
152
+ ),
153
+ document_uids=get_document_uids(runtime_context),
154
+ search_policy=get_search_policy(runtime_context),
155
+ owner_filter=OwnerFilter.TEAM if scoped_team else OwnerFilter.PERSONAL,
156
+ team_id=team_id if scoped_team else None,
157
+ session_id=runtime_context.session_id,
158
+ include_session_scope=include_session_scope,
159
+ include_corpus_scope=include_corpus_scope,
160
+ )
161
+
162
+ logger.info(
163
+ "[KFVS][TOOL] question=%r top_k=%d hits=%d",
164
+ question[:80],
165
+ top_k,
166
+ len(hits),
167
+ )
168
+ # Return a ToolInvocationResult directly: the runtime-provider resolver
169
+ # invokes this tool with a plain args dict, so a content_and_artifact
170
+ # tuple would lose the artifact (and its sources). `blocks` feed the LLM
171
+ # the hit JSON; `sources` carry the typed hits the Sources panel renders.
172
+ return ToolInvocationResult(
173
+ tool_ref=KF_VECTOR_SEARCH_PROVIDER,
174
+ blocks=(
175
+ ToolContentBlock(
176
+ kind=ToolContentKind.JSON,
177
+ data={
178
+ "query": question,
179
+ "hits": [_llm_slice(h) for h in hits],
180
+ },
181
+ ),
182
+ ),
183
+ sources=tuple(hits),
184
+ )
185
+
186
+ return [search_documents_using_vectorization]