fred-runtime 2.0.7__tar.gz → 2.0.10__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 (112) hide show
  1. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/PKG-INFO +5 -2
  2. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/agent_app.py +127 -49
  3. fred_runtime-2.0.10/fred_runtime/app/container.py +27 -0
  4. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/context.py +14 -0
  5. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/dependencies.py +14 -0
  6. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/mcp_config.py +14 -0
  7. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/__init__.py +14 -0
  8. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/completion.py +14 -0
  9. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/entrypoint.py +14 -0
  10. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/history_display.py +14 -0
  11. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/kpi_display.py +14 -0
  12. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/pod_client.py +14 -0
  13. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/repl.py +14 -0
  14. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/repl_helpers.py +14 -0
  15. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/cli/url_helpers.py +14 -0
  16. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/client.py +14 -0
  17. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/context_aware_tool.py +21 -5
  18. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/token_expiry.py +14 -0
  19. fred_runtime-2.0.10/fred_runtime/deep/__init__.py +15 -0
  20. fred_runtime-2.0.10/fred_runtime/graph/__init__.py +15 -0
  21. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/graph/graph_runtime.py +14 -0
  22. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/integrations/v2_runtime/adapters.py +19 -1
  23. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/model_routing/__init__.py +14 -0
  24. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/model_routing/catalog.py +14 -0
  25. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/model_routing/provider.py +14 -0
  26. fred_runtime-2.0.10/fred_runtime/react/__init__.py +15 -0
  27. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_message_codec.py +14 -0
  28. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_model_adapter.py +14 -0
  29. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tool_rendering.py +14 -0
  30. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tool_utils.py +14 -0
  31. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/checkpoints.py +14 -0
  32. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/sql_checkpointer.py +14 -0
  33. fred_runtime-2.0.10/fred_runtime/support/__init__.py +15 -0
  34. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/support/filesystem_context.py +14 -0
  35. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/support/tool_approval.py +14 -0
  36. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/support/tool_loop.py +14 -0
  37. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/PKG-INFO +5 -2
  38. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/SOURCES.txt +1 -0
  39. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/requires.txt +4 -1
  40. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/pyproject.toml +10 -2
  41. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_agent_app.py +14 -0
  42. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_client.py +14 -0
  43. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_config_loader.py +14 -0
  44. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_context.py +14 -0
  45. fred_runtime-2.0.10/tests/test_context_aware_tool.py +78 -0
  46. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_conversational_memory.py +14 -0
  47. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_eval_collector.py +14 -0
  48. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_eval_trace.py +14 -0
  49. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_graph_runtime_observability.py +14 -0
  50. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_history.py +184 -2
  51. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_kpi_display.py +14 -0
  52. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_mcp_config.py +14 -0
  53. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_model_routing.py +14 -0
  54. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_openai_compat_router.py +14 -0
  55. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_pod_client.py +14 -0
  56. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_repl_helpers.py +14 -0
  57. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_smoke.py +14 -0
  58. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_token_expiry.py +14 -0
  59. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_url_helpers.py +14 -0
  60. fred_runtime-2.0.7/fred_runtime/app/container.py +0 -13
  61. fred_runtime-2.0.7/fred_runtime/deep/__init__.py +0 -1
  62. fred_runtime-2.0.7/fred_runtime/graph/__init__.py +0 -1
  63. fred_runtime-2.0.7/fred_runtime/react/__init__.py +0 -1
  64. fred_runtime-2.0.7/fred_runtime/support/__init__.py +0 -1
  65. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/README.md +0 -0
  66. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/__init__.py +0 -0
  67. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/__init__.py +0 -0
  68. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/_catalogs.py +0 -0
  69. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/config.py +0 -0
  70. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/config_loader.py +0 -0
  71. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/observability_factory.py +0 -0
  72. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/app/openai_compat_router.py +0 -0
  73. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/__init__.py +0 -0
  74. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_base_client.py +0 -0
  75. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_fast_text_client.py +0 -0
  76. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_http_client.py +0 -0
  77. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_logs_client.py +0 -0
  78. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_markdown_media_client.py +0 -0
  79. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
  80. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/kf_workspace_client.py +0 -0
  81. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/mcp_interceptors.py +0 -0
  82. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/mcp_runtime.py +0 -0
  83. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/mcp_toolkit.py +0 -0
  84. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/mcp_utils.py +0 -0
  85. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/structures.py +0 -0
  86. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/common/tool_node_utils.py +0 -0
  87. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/deep/deep_runtime.py +0 -0
  88. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/eval/__init__.py +0 -0
  89. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/eval/collector.py +0 -0
  90. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/integrations/__init__.py +0 -0
  91. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
  92. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/model_routing/contracts.py +0 -0
  93. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/model_routing/resolver.py +0 -0
  94. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_langchain_adapter.py +0 -0
  95. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_prompting.py +0 -0
  96. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_runtime.py +0 -0
  97. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_stream_adapter.py +0 -0
  98. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tool_binding.py +0 -0
  99. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tool_loop.py +0 -0
  100. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tool_resolution.py +0 -0
  101. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/react/react_tracing.py +0 -0
  102. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_context.py +0 -0
  103. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/__init__.py +0 -0
  104. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/model_metadata.py +0 -0
  105. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
  106. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
  107. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/dependency_links.txt +0 -0
  108. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/entry_points.txt +0 -0
  109. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/fred_runtime.egg-info/top_level.txt +0 -0
  110. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/setup.cfg +0 -0
  111. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/tests/test_kf_workspace_client.py +0 -0
  112. {fred_runtime-2.0.7 → fred_runtime-2.0.10}/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.7
3
+ Version: 2.0.10
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
@@ -814,6 +814,7 @@ class _AgentTemplateSummary(BaseModel):
814
814
  template_agent_id: str
815
815
  title: str
816
816
  description: str
817
+ description_by_lang: dict[str, str] | None = None
817
818
  kind: ExecutionCategory
818
819
  default_tuning: AgentTuning
819
820
  available_mcp_servers: list[MCPServerConfiguration] = Field(default_factory=list)
@@ -2060,6 +2061,7 @@ def _build_agent_router(
2060
2061
  template_agent_id=definition.agent_id,
2061
2062
  title=definition.role,
2062
2063
  description=definition.description,
2064
+ description_by_lang=getattr(definition, "description_by_lang", None),
2063
2065
  kind=definition.execution_category,
2064
2066
  default_tuning=_definition_to_agent_tuning(definition),
2065
2067
  available_mcp_servers=_available_mcp_servers_for_definition(definition),
@@ -2101,22 +2103,20 @@ def _build_agent_router(
2101
2103
  ]
2102
2104
  )
2103
2105
 
2104
- @router.get("/sessions", dependencies=_auth_deps)
2105
- async def list_sessions(user_id: str) -> list[str]:
2106
+ @router.get("/sessions")
2107
+ async def list_sessions(
2108
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2109
+ user_id: str | None = None,
2110
+ ) -> list[str]:
2106
2111
  """
2107
- Return the session IDs for a user, most recent first.
2112
+ Return the session IDs for the authenticated user, most recent first.
2108
2113
 
2109
- GET <configured base_url>/agents/sessions?user_id=<user_id>
2110
- Authorization: Bearer <user JWT> (same auth as execute endpoints)
2111
- Response: JSON array of session_id strings
2112
-
2113
- Why this endpoint exists:
2114
- - the UI needs to list past conversations for a returning user
2115
- - the checkpointer has no user_id index; only the history store does
2114
+ GET <configured base_url>/agents/sessions
2115
+ Authorization: Bearer <user JWT>
2116
2116
 
2117
- How to use it:
2118
- - GET /agents/sessions?user_id=alice
2119
- - returns ["session-3", "session-1", ...] most recent first
2117
+ Security: user identity is always extracted from the JWT token.
2118
+ The user_id query parameter is accepted only in dev mode (security
2119
+ disabled) for CLI convenience; it is ignored when security is enabled.
2120
2120
  """
2121
2121
  history_store = get_runtime_context().config.history_store
2122
2122
  if history_store is None:
@@ -2124,29 +2124,30 @@ def _build_agent_router(
2124
2124
  status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
2125
2125
  detail="No history store configured — session listing is unavailable.",
2126
2126
  )
2127
- return await history_store.list_sessions(user_id=user_id)
2127
+ effective_uid = caller.uid if caller is not None else user_id
2128
+ if effective_uid is None:
2129
+ return []
2130
+ return await history_store.list_sessions(user_id=effective_uid)
2128
2131
 
2129
2132
  @router.get(
2130
2133
  "/sessions/{session_id}/messages",
2131
- dependencies=_auth_deps,
2132
2134
  response_model=list[ChatMessage],
2133
2135
  )
2134
- async def get_session_messages(session_id: str) -> list[ChatMessage]:
2136
+ async def get_session_messages(
2137
+ session_id: str,
2138
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2139
+ ) -> list[ChatMessage]:
2135
2140
  """
2136
2141
  Return the conversation history for a session as a flat message list.
2137
2142
 
2138
2143
  GET <configured base_url>/agents/sessions/{session_id}/messages
2139
- Authorization: Bearer <user JWT> (same auth as execute endpoints)
2140
- Response: JSON array of ChatMessage objects (role/channel/parts/metadata).
2144
+ Authorization: Bearer <user JWT>
2141
2145
 
2142
- Why the history store is the source of truth:
2143
- - the history store writes one row per message, keyed by (session_id, rank)
2144
- - it is agent-type-agnostic and works for both ReAct and Graph agents
2145
- - the checkpointer (former source) only works for agents with a ``messages``
2146
- channel in state and is not queryable per user_id
2146
+ Security: only rows belonging to the authenticated user are returned.
2147
+ Returns [] when the session does not exist or belongs to another user
2148
+ callers cannot distinguish the two cases by design.
2147
2149
 
2148
2150
  Returns 503 when no history store is configured (stateless pod mode).
2149
- Returns [] when the session exists but has no rows yet.
2150
2151
  """
2151
2152
  history_store = get_runtime_context().config.history_store
2152
2153
  if history_store is None:
@@ -2154,27 +2155,29 @@ def _build_agent_router(
2154
2155
  status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
2155
2156
  detail="No history store configured — session history is unavailable.",
2156
2157
  )
2157
- return await history_store.get(session_id=session_id)
2158
+ caller_uid = caller.uid if caller is not None else None
2159
+ return await history_store.get(session_id=session_id, user_id=caller_uid)
2158
2160
 
2159
2161
  @router.delete(
2160
2162
  "/sessions/{session_id}",
2161
- dependencies=_auth_deps,
2162
2163
  status_code=status.HTTP_200_OK,
2163
2164
  )
2164
- async def delete_session_history(session_id: str) -> dict[str, int]:
2165
+ async def delete_session_history(
2166
+ session_id: str,
2167
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2168
+ ) -> dict[str, int]:
2165
2169
  """
2166
- Permanently delete all history rows for a session.
2170
+ Permanently delete history rows for a session.
2167
2171
 
2168
2172
  DELETE <base_url>/agents/sessions/{session_id}
2169
2173
 
2170
- Why this endpoint exists:
2171
- - developers need to clean up test sessions from the CLI without
2172
- restarting the pod or connecting to the database directly
2173
- - devops need to reclaim storage from stale or abandoned conversations
2174
- - checkpoint state is NOT touched; delete the checkpoint separately
2175
- via DELETE /agents/checkpoints/{session_id} if required
2174
+ Security: only rows belonging to the authenticated user are deleted.
2175
+ Returns {"deleted": 0} when the session does not exist or belongs to
2176
+ another user callers cannot distinguish the two cases by design.
2177
+
2178
+ Checkpoint state is NOT touched; delete separately via
2179
+ DELETE /agents/checkpoints/{session_id} if required.
2176
2180
 
2177
- Returns {"deleted": N} where N is the number of rows removed.
2178
2181
  Returns 503 when no history store is configured.
2179
2182
  """
2180
2183
  history_store = get_runtime_context().config.history_store
@@ -2183,7 +2186,10 @@ def _build_agent_router(
2183
2186
  status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
2184
2187
  detail="No history store configured — session history is unavailable.",
2185
2188
  )
2186
- count = await history_store.delete_session(session_id=session_id)
2189
+ caller_uid = caller.uid if caller is not None else None
2190
+ count = await history_store.delete_session(
2191
+ session_id=session_id, user_id=caller_uid
2192
+ )
2187
2193
  return {"deleted": count}
2188
2194
 
2189
2195
  # ------------------------------------------------------------------
@@ -2199,8 +2205,36 @@ def _build_agent_router(
2199
2205
  )
2200
2206
  return cp
2201
2207
 
2202
- @router.get("/checkpoints", dependencies=_auth_deps)
2208
+ def _get_history_store_for_owned_access(
2209
+ caller: KeycloakUser | None,
2210
+ ) -> HistoryStorePort | None:
2211
+ """Return the history store used as ownership oracle, failing closed.
2212
+
2213
+ Why this helper exists:
2214
+ - checkpoint tables do not carry user_id, so ownership checks depend on
2215
+ the history store
2216
+ - when security is enabled, proceeding without that oracle would leak
2217
+ checkpoint visibility across users
2218
+
2219
+ How to use it:
2220
+ - call from checkpoint endpoints before listing or mutating per-session
2221
+ checkpoint data
2222
+ """
2223
+ if caller is None:
2224
+ return None
2225
+ history_store = get_runtime_context().config.history_store
2226
+ if history_store is None:
2227
+ raise HTTPException(
2228
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
2229
+ detail=(
2230
+ "No history store configured — ownership checks are unavailable."
2231
+ ),
2232
+ )
2233
+ return history_store
2234
+
2235
+ @router.get("/checkpoints")
2203
2236
  async def list_checkpoint_threads(
2237
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2204
2238
  limit: int = 100,
2205
2239
  ) -> list[_CheckpointThreadSummary]:
2206
2240
  """
@@ -2271,6 +2305,15 @@ def _build_agent_router(
2271
2305
  .limit(limit)
2272
2306
  )
2273
2307
  rows = (await conn.execute(stmt)).fetchall()
2308
+
2309
+ # Scope to the caller's own sessions using the history store as the
2310
+ # ownership oracle (checkpoint tables carry no user_id column).
2311
+ caller_uid = caller.uid if caller is not None else None
2312
+ history_store = _get_history_store_for_owned_access(caller)
2313
+ if caller_uid is not None and history_store is not None:
2314
+ owned = set(await history_store.list_sessions(user_id=caller_uid))
2315
+ rows = [r for r in rows if str(r.thread_id) in owned]
2316
+
2274
2317
  return [
2275
2318
  _CheckpointThreadSummary(
2276
2319
  session_id=str(row.thread_id),
@@ -2338,8 +2381,11 @@ def _build_agent_router(
2338
2381
  blob_bytes_approx=int(blob_bytes),
2339
2382
  )
2340
2383
 
2341
- @router.get("/checkpoints/{session_id}", dependencies=_auth_deps)
2342
- async def get_checkpoint_thread(session_id: str) -> _CheckpointThreadDetail:
2384
+ @router.get("/checkpoints/{session_id}")
2385
+ async def get_checkpoint_thread(
2386
+ session_id: str,
2387
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2388
+ ) -> _CheckpointThreadDetail:
2343
2389
  """
2344
2390
  Return all checkpoints for one session, newest first.
2345
2391
 
@@ -2360,7 +2406,20 @@ def _build_agent_router(
2360
2406
 
2361
2407
  Returns 503 when no checkpointer is configured.
2362
2408
  Returns an empty checkpoints list when the session has no rows.
2409
+ Returns 403 when the session does not belong to the authenticated user.
2363
2410
  """
2411
+ caller_uid = caller.uid if caller is not None else None
2412
+ history_store = _get_history_store_for_owned_access(caller)
2413
+ if (
2414
+ caller_uid is not None
2415
+ and history_store is not None
2416
+ and not await history_store.session_belongs_to_user(session_id, caller_uid)
2417
+ ):
2418
+ raise HTTPException(
2419
+ status_code=status.HTTP_403_FORBIDDEN,
2420
+ detail="Access denied.",
2421
+ )
2422
+
2364
2423
  from sqlalchemy import desc, func, select
2365
2424
 
2366
2425
  cp = _get_checkpointer()
@@ -2429,25 +2488,38 @@ def _build_agent_router(
2429
2488
 
2430
2489
  @router.delete(
2431
2490
  "/checkpoints/{session_id}",
2432
- dependencies=_auth_deps,
2433
2491
  status_code=status.HTTP_204_NO_CONTENT,
2434
2492
  response_model=None,
2435
2493
  )
2436
- async def delete_checkpoint_thread(session_id: str) -> None:
2494
+ async def delete_checkpoint_thread(
2495
+ session_id: str,
2496
+ caller: KeycloakUser | None = Depends(_authenticated_user),
2497
+ ) -> None:
2437
2498
  """
2438
2499
  Purge all checkpoint data for one session.
2439
2500
 
2440
2501
  DELETE <base_url>/agents/checkpoints/{session_id}
2441
2502
 
2442
- Deletes all rows in the checkpoints, blobs, and writes tables for the
2443
- given session_id. This is irreversible the agent will lose the ability
2444
- to resume from any prior HITL pause or conversation state for this session.
2503
+ Security: the session must belong to the authenticated user.
2504
+ Returns 403 when ownership cannot be confirmed via the history store.
2445
2505
 
2446
- History store rows (session_history) are NOT deleted only checkpoint
2447
- state is purged. Use this to reclaim storage from stale or test sessions.
2506
+ Deletes all rows in the checkpoints, blobs, and writes tables.
2507
+ History store rows are NOT deleted use DELETE /sessions/{session_id}
2508
+ to remove those separately.
2448
2509
 
2449
- Returns 204 on success, 503 when no checkpointer is configured.
2510
+ Returns 204 on success, 403 when not owned, 503 when no checkpointer.
2450
2511
  """
2512
+ caller_uid = caller.uid if caller is not None else None
2513
+ history_store = _get_history_store_for_owned_access(caller)
2514
+ if (
2515
+ caller_uid is not None
2516
+ and history_store is not None
2517
+ and not await history_store.session_belongs_to_user(session_id, caller_uid)
2518
+ ):
2519
+ raise HTTPException(
2520
+ status_code=status.HTTP_403_FORBIDDEN,
2521
+ detail="Access denied.",
2522
+ )
2451
2523
  cp = _get_checkpointer()
2452
2524
  await cp.adelete_thread(session_id)
2453
2525
 
@@ -2850,6 +2922,12 @@ def create_agent_app(
2850
2922
  await container.initialize_sql()
2851
2923
  container.start_metrics_exporter()
2852
2924
  await container.start_kpi_tasks()
2925
+ checkpointer = container.get_checkpointer()
2926
+ history_store = container.get_history_store()
2927
+ if (checkpointer is None) != (history_store is None):
2928
+ raise RuntimeError(
2929
+ "Invalid runtime storage state: checkpointer and history store must be configured together."
2930
+ )
2853
2931
  set_runtime_context(
2854
2932
  FredRuntimeContext(
2855
2933
  RuntimeConfig(
@@ -2857,8 +2935,8 @@ def create_agent_app(
2857
2935
  service_name=config.app.name,
2858
2936
  timeouts=config.ai.timeout,
2859
2937
  chat_model_factory=chat_factory,
2860
- checkpointer=container.get_checkpointer(),
2861
- history_store=container.get_history_store(),
2938
+ checkpointer=checkpointer,
2939
+ history_store=history_store,
2862
2940
  mcp_configuration=config.get_mcp_configuration(),
2863
2941
  control_plane_url=config.platform.control_plane_url,
2864
2942
  kpi_writer=container.get_kpi_writer(),
@@ -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(