fred-runtime 2.0.4__tar.gz → 2.0.7__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 (106) hide show
  1. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/PKG-INFO +51 -36
  2. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/README.md +50 -35
  3. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/_catalogs.py +31 -1
  4. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/agent_app.py +63 -29
  5. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/config.py +9 -0
  6. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/completion.py +1 -1
  7. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/context_aware_tool.py +35 -2
  8. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_workspace_client.py +18 -6
  9. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/deep/deep_runtime.py +0 -3
  10. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/graph/graph_runtime.py +135 -11
  11. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/integrations/v2_runtime/adapters.py +73 -47
  12. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_prompting.py +25 -38
  13. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_runtime.py +53 -2
  14. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/PKG-INFO +51 -36
  15. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/pyproject.toml +1 -1
  16. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_agent_app.py +186 -2
  17. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_config_loader.py +81 -3
  18. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_conversational_memory.py +3 -0
  19. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_graph_runtime_observability.py +11 -1
  20. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_mcp_config.py +33 -1
  21. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_model_routing.py +13 -6
  22. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/__init__.py +0 -0
  23. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/__init__.py +0 -0
  24. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/config_loader.py +0 -0
  25. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/container.py +0 -0
  26. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/context.py +0 -0
  27. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/dependencies.py +0 -0
  28. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/mcp_config.py +0 -0
  29. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/observability_factory.py +0 -0
  30. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/app/openai_compat_router.py +0 -0
  31. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/__init__.py +0 -0
  32. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/entrypoint.py +0 -0
  33. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/history_display.py +0 -0
  34. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/kpi_display.py +0 -0
  35. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/pod_client.py +0 -0
  36. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/repl.py +0 -0
  37. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/repl_helpers.py +0 -0
  38. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/cli/url_helpers.py +0 -0
  39. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/client.py +0 -0
  40. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/__init__.py +0 -0
  41. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_base_client.py +0 -0
  42. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_fast_text_client.py +0 -0
  43. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_http_client.py +0 -0
  44. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_logs_client.py +0 -0
  45. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_markdown_media_client.py +0 -0
  46. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
  47. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/mcp_interceptors.py +0 -0
  48. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/mcp_runtime.py +0 -0
  49. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/mcp_toolkit.py +0 -0
  50. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/mcp_utils.py +0 -0
  51. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/structures.py +0 -0
  52. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/token_expiry.py +0 -0
  53. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/common/tool_node_utils.py +0 -0
  54. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/deep/__init__.py +0 -0
  55. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/eval/__init__.py +0 -0
  56. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/eval/collector.py +0 -0
  57. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/graph/__init__.py +0 -0
  58. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/integrations/__init__.py +0 -0
  59. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
  60. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/model_routing/__init__.py +0 -0
  61. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/model_routing/catalog.py +0 -0
  62. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/model_routing/contracts.py +0 -0
  63. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/model_routing/provider.py +0 -0
  64. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/model_routing/resolver.py +0 -0
  65. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/__init__.py +0 -0
  66. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_langchain_adapter.py +0 -0
  67. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_message_codec.py +0 -0
  68. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_model_adapter.py +0 -0
  69. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_stream_adapter.py +0 -0
  70. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tool_binding.py +0 -0
  71. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tool_loop.py +0 -0
  72. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tool_rendering.py +0 -0
  73. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tool_resolution.py +0 -0
  74. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tool_utils.py +0 -0
  75. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/react/react_tracing.py +0 -0
  76. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_context.py +0 -0
  77. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/__init__.py +0 -0
  78. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/checkpoints.py +0 -0
  79. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/model_metadata.py +0 -0
  80. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
  81. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
  82. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
  83. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/support/__init__.py +0 -0
  84. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/support/filesystem_context.py +0 -0
  85. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/support/tool_approval.py +0 -0
  86. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime/support/tool_loop.py +0 -0
  87. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/SOURCES.txt +0 -0
  88. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/dependency_links.txt +0 -0
  89. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/entry_points.txt +0 -0
  90. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/requires.txt +0 -0
  91. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/fred_runtime.egg-info/top_level.txt +0 -0
  92. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/setup.cfg +0 -0
  93. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_client.py +0 -0
  94. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_context.py +0 -0
  95. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_eval_collector.py +0 -0
  96. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_eval_trace.py +0 -0
  97. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_history.py +0 -0
  98. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_kf_workspace_client.py +0 -0
  99. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_kpi_display.py +0 -0
  100. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_openai_compat_router.py +0 -0
  101. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_pod_client.py +0 -0
  102. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_repl_helpers.py +0 -0
  103. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_smoke.py +0 -0
  104. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_token_expiry.py +0 -0
  105. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/tests/test_url_helpers.py +0 -0
  106. {fred_runtime-2.0.4 → fred_runtime-2.0.7}/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.4
3
+ Version: 2.0.7
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
@@ -58,8 +58,9 @@ fred-runtime Platform adapters + pod factory (this package)
58
58
  ```
59
59
 
60
60
  **Rule of thumb:**
61
- - Write agent logic in `fred-sdk`.
62
- - Write infrastructure adapters (DB, MCP server, Keycloak, object store) in `fred-runtime`.
61
+
62
+ - Write agent logic in `fred-sdk`.
63
+ - Write infrastructure adapters (DB, MCP server, Keycloak, object store) in `fred-runtime`.
63
64
  - `fred-sdk` must stay importable on a bare laptop with no services running.
64
65
 
65
66
  ---
@@ -79,15 +80,15 @@ app = create_agent_app(registry=REGISTRY, config=config)
79
80
 
80
81
  `create_agent_app` returns a FastAPI application that exposes:
81
82
 
82
- | Method | Path | Description |
83
- |--------|------|-------------|
84
- | `POST` | `{base_url}/agents/execute` | Single-turn execution — returns final JSON |
85
- | `POST` | `{base_url}/agents/execute/stream` | Streaming SSE execution — yields `RuntimeEvent` objects |
86
- | `GET` | `{base_url}/agents` | List registered agent IDs |
87
- | `GET` | `{base_url}/agents/sessions` | List session IDs for a user |
88
- | `GET` | `{base_url}/agents/sessions/{id}/messages` | Full conversation history for a session |
89
- | `GET` | `/v1/models` | OpenAI model list (agent IDs as model names) |
90
- | `POST` | `/v1/chat/completions` | OpenAI chat completions — works with Open WebUI, openai-python SDK, etc. |
83
+ | Method | Path | Description |
84
+ | ------ | ------------------------------------------ | ------------------------------------------------------------------------ |
85
+ | `POST` | `{base_url}/agents/execute` | Single-turn execution — returns final JSON |
86
+ | `POST` | `{base_url}/agents/execute/stream` | Streaming SSE execution — yields `RuntimeEvent` objects |
87
+ | `GET` | `{base_url}/agents` | List registered agent IDs |
88
+ | `GET` | `{base_url}/agents/sessions` | List session IDs for a user |
89
+ | `GET` | `{base_url}/agents/sessions/{id}/messages` | Full conversation history for a session |
90
+ | `GET` | `/v1/models` | OpenAI model list (agent IDs as model names) |
91
+ | `POST` | `/v1/chat/completions` | OpenAI chat completions — works with Open WebUI, openai-python SDK, etc. |
91
92
 
92
93
  The OpenAI-compatible `/v1` surface is **enabled by default**.
93
94
  Set `app.openai_compat: false` in `configuration.yaml` to disable it for internal pods.
@@ -99,11 +100,11 @@ the SQL checkpointer. The session ID is the LangGraph `thread_id`.
99
100
 
100
101
  ### `fred_runtime.runtime_support` — Infrastructure adapters
101
102
 
102
- | Module | What it provides |
103
- |--------|-----------------|
104
- | `sql_checkpointer` | Durable LangGraph checkpointer backed by SQLite (dev) or PostgreSQL (prod) |
105
- | `user_token_refresher` | Transparent Keycloak token refresh for long-lived agent sessions |
106
- | `request_context_helpers` | FastAPI dependency helpers for extracting user/session context |
103
+ | Module | What it provides |
104
+ | ------------------------- | -------------------------------------------------------------------------- |
105
+ | `sql_checkpointer` | Durable LangGraph checkpointer backed by SQLite (dev) or PostgreSQL (prod) |
106
+ | `user_token_refresher` | Transparent Keycloak token refresh for long-lived agent sessions |
107
+ | `request_context_helpers` | FastAPI dependency helpers for extracting user/session context |
107
108
 
108
109
  ---
109
110
 
@@ -119,16 +120,16 @@ Providers: OpenAI, Azure OpenAI, Mistral, Ollama, and any LangChain-compatible b
119
120
 
120
121
  HTTP clients that connect agent tools to the Fred platform services:
121
122
 
122
- | Client | Connects to |
123
- |--------|------------|
124
- | `kf_http_client` | Knowledge Flow REST API (generic) |
125
- | `kf_vectorsearch_client` | Vector search / retrieval |
126
- | `kf_markdown_media_client` | Document content (Markdown + media) |
127
- | `kf_workspace_client` | Workspace and library management |
128
- | `kf_logs_client` | Audit log retrieval |
129
- | `kf_fast_text_client` | FastText classification |
130
- | `mcp_runtime` / `mcp_toolkit` | MCP server lifecycle and tool injection |
131
- | `context_aware_tool` | Tool base class that propagates the runtime context (user, team, token) |
123
+ | Client | Connects to |
124
+ | ----------------------------- | ----------------------------------------------------------------------- |
125
+ | `kf_http_client` | Knowledge Flow REST API (generic) |
126
+ | `kf_vectorsearch_client` | Vector search / retrieval |
127
+ | `kf_markdown_media_client` | Document content (Markdown + media) |
128
+ | `kf_workspace_client` | Workspace and library management |
129
+ | `kf_logs_client` | Audit log retrieval |
130
+ | `kf_fast_text_client` | FastText classification |
131
+ | `mcp_runtime` / `mcp_toolkit` | MCP server lifecycle and tool injection |
132
+ | `context_aware_tool` | Tool base class that propagates the runtime context (user, team, token) |
132
133
 
133
134
  ---
134
135
 
@@ -176,9 +177,9 @@ or overridden with `--base-url` / `FRED_AGENT_POD_URL`.
176
177
 
177
178
  Every Fred pod uses the same two-file convention:
178
179
 
179
- | File | Purpose |
180
- |------|---------|
181
- | `.env` (path from `ENV_FILE`) | Secrets: API keys, DB URLs, Keycloak credentials |
180
+ | File | Purpose |
181
+ | ---------------------------------------------- | ------------------------------------------------------------------ |
182
+ | `.env` (path from `ENV_FILE`) | Secrets: API keys, DB URLs, Keycloak credentials |
182
183
  | `configuration.yaml` (path from `CONFIG_FILE`) | App settings: port, base URL, LLM routing, observability, security |
183
184
 
184
185
  Minimal `configuration.yaml` for a local pod:
@@ -190,6 +191,7 @@ app:
190
191
  host: "0.0.0.0"
191
192
  port: 8010
192
193
  log_level: "info"
194
+ limit_concurrency: 200
193
195
  metrics_address: "127.0.0.1"
194
196
  metrics_port: 9115
195
197
  kpi_process_metrics_interval_sec: 10
@@ -208,6 +210,10 @@ When `observability.metrics: prometheus` is enabled, `create_agent_app(...)`
208
210
  starts a dedicated Prometheus exporter on `app.metrics_address:app.metrics_port`
209
211
  and restores the shared Fred KPI pipeline, including process and SQL pool KPIs.
210
212
 
213
+ Set `app.limit_concurrency: null` to disable Uvicorn connection limiting, or a
214
+ positive integer to reject excess concurrent HTTP and WebSocket connections
215
+ with `503` before application code runs.
216
+
211
217
  ---
212
218
 
213
219
  ## Installation
@@ -229,6 +235,7 @@ Requires Python 3.12.
229
235
  A minimal pod is three files:
230
236
 
231
237
  **`main.py`**
238
+
232
239
  ```python
233
240
  from fred_runtime.app import create_agent_app, load_agent_pod_config
234
241
  from myapp.registry import REGISTRY
@@ -238,19 +245,27 @@ app = create_agent_app(registry=REGISTRY, config=config)
238
245
  ```
239
246
 
240
247
  **`__main__.py`**
248
+
241
249
  ```python
242
250
  import uvicorn
243
251
  from fred_runtime.app import load_agent_pod_config
244
252
 
245
253
  def main():
246
254
  config = load_agent_pod_config()
247
- uvicorn.run("myapp.main:app", host=config.app.host, port=config.app.port, reload=True)
255
+ uvicorn.run(
256
+ "myapp.main:app",
257
+ host=config.app.host,
258
+ port=config.app.port,
259
+ limit_concurrency=config.app.limit_concurrency,
260
+ reload=True,
261
+ )
248
262
 
249
263
  if __name__ == "__main__":
250
264
  main()
251
265
  ```
252
266
 
253
267
  **`registry.py`**
268
+
254
269
  ```python
255
270
  from fred_sdk.contracts.models import ReActAgentDefinition
256
271
 
@@ -267,11 +282,11 @@ See [fred-samples](https://github.com/ThalesGroup/fred) for a working reference
267
282
 
268
283
  ## Related packages
269
284
 
270
- | Package | PyPI | Role |
271
- |---------|------|------|
272
- | `fred-core` | [pypi](https://pypi.org/project/fred-core/) | Pure utilities — logging, model factories, embeddings, portable observability |
273
- | `fred-sdk` | [pypi](https://pypi.org/project/fred-sdk/) | Agent authoring — ReAct, Graph, tool contracts |
274
- | `fred-runtime` | [pypi](https://pypi.org/project/fred-runtime/) | This package |
285
+ | Package | PyPI | Role |
286
+ | -------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- |
287
+ | `fred-core` | [pypi](https://pypi.org/project/fred-core/) | Pure utilities — logging, model factories, embeddings, portable observability |
288
+ | `fred-sdk` | [pypi](https://pypi.org/project/fred-sdk/) | Agent authoring — ReAct, Graph, tool contracts |
289
+ | `fred-runtime` | [pypi](https://pypi.org/project/fred-runtime/) | This package |
275
290
 
276
291
  ---
277
292
 
@@ -23,8 +23,9 @@ fred-runtime Platform adapters + pod factory (this package)
23
23
  ```
24
24
 
25
25
  **Rule of thumb:**
26
- - Write agent logic in `fred-sdk`.
27
- - Write infrastructure adapters (DB, MCP server, Keycloak, object store) in `fred-runtime`.
26
+
27
+ - Write agent logic in `fred-sdk`.
28
+ - Write infrastructure adapters (DB, MCP server, Keycloak, object store) in `fred-runtime`.
28
29
  - `fred-sdk` must stay importable on a bare laptop with no services running.
29
30
 
30
31
  ---
@@ -44,15 +45,15 @@ app = create_agent_app(registry=REGISTRY, config=config)
44
45
 
45
46
  `create_agent_app` returns a FastAPI application that exposes:
46
47
 
47
- | Method | Path | Description |
48
- |--------|------|-------------|
49
- | `POST` | `{base_url}/agents/execute` | Single-turn execution — returns final JSON |
50
- | `POST` | `{base_url}/agents/execute/stream` | Streaming SSE execution — yields `RuntimeEvent` objects |
51
- | `GET` | `{base_url}/agents` | List registered agent IDs |
52
- | `GET` | `{base_url}/agents/sessions` | List session IDs for a user |
53
- | `GET` | `{base_url}/agents/sessions/{id}/messages` | Full conversation history for a session |
54
- | `GET` | `/v1/models` | OpenAI model list (agent IDs as model names) |
55
- | `POST` | `/v1/chat/completions` | OpenAI chat completions — works with Open WebUI, openai-python SDK, etc. |
48
+ | Method | Path | Description |
49
+ | ------ | ------------------------------------------ | ------------------------------------------------------------------------ |
50
+ | `POST` | `{base_url}/agents/execute` | Single-turn execution — returns final JSON |
51
+ | `POST` | `{base_url}/agents/execute/stream` | Streaming SSE execution — yields `RuntimeEvent` objects |
52
+ | `GET` | `{base_url}/agents` | List registered agent IDs |
53
+ | `GET` | `{base_url}/agents/sessions` | List session IDs for a user |
54
+ | `GET` | `{base_url}/agents/sessions/{id}/messages` | Full conversation history for a session |
55
+ | `GET` | `/v1/models` | OpenAI model list (agent IDs as model names) |
56
+ | `POST` | `/v1/chat/completions` | OpenAI chat completions — works with Open WebUI, openai-python SDK, etc. |
56
57
 
57
58
  The OpenAI-compatible `/v1` surface is **enabled by default**.
58
59
  Set `app.openai_compat: false` in `configuration.yaml` to disable it for internal pods.
@@ -64,11 +65,11 @@ the SQL checkpointer. The session ID is the LangGraph `thread_id`.
64
65
 
65
66
  ### `fred_runtime.runtime_support` — Infrastructure adapters
66
67
 
67
- | Module | What it provides |
68
- |--------|-----------------|
69
- | `sql_checkpointer` | Durable LangGraph checkpointer backed by SQLite (dev) or PostgreSQL (prod) |
70
- | `user_token_refresher` | Transparent Keycloak token refresh for long-lived agent sessions |
71
- | `request_context_helpers` | FastAPI dependency helpers for extracting user/session context |
68
+ | Module | What it provides |
69
+ | ------------------------- | -------------------------------------------------------------------------- |
70
+ | `sql_checkpointer` | Durable LangGraph checkpointer backed by SQLite (dev) or PostgreSQL (prod) |
71
+ | `user_token_refresher` | Transparent Keycloak token refresh for long-lived agent sessions |
72
+ | `request_context_helpers` | FastAPI dependency helpers for extracting user/session context |
72
73
 
73
74
  ---
74
75
 
@@ -84,16 +85,16 @@ Providers: OpenAI, Azure OpenAI, Mistral, Ollama, and any LangChain-compatible b
84
85
 
85
86
  HTTP clients that connect agent tools to the Fred platform services:
86
87
 
87
- | Client | Connects to |
88
- |--------|------------|
89
- | `kf_http_client` | Knowledge Flow REST API (generic) |
90
- | `kf_vectorsearch_client` | Vector search / retrieval |
91
- | `kf_markdown_media_client` | Document content (Markdown + media) |
92
- | `kf_workspace_client` | Workspace and library management |
93
- | `kf_logs_client` | Audit log retrieval |
94
- | `kf_fast_text_client` | FastText classification |
95
- | `mcp_runtime` / `mcp_toolkit` | MCP server lifecycle and tool injection |
96
- | `context_aware_tool` | Tool base class that propagates the runtime context (user, team, token) |
88
+ | Client | Connects to |
89
+ | ----------------------------- | ----------------------------------------------------------------------- |
90
+ | `kf_http_client` | Knowledge Flow REST API (generic) |
91
+ | `kf_vectorsearch_client` | Vector search / retrieval |
92
+ | `kf_markdown_media_client` | Document content (Markdown + media) |
93
+ | `kf_workspace_client` | Workspace and library management |
94
+ | `kf_logs_client` | Audit log retrieval |
95
+ | `kf_fast_text_client` | FastText classification |
96
+ | `mcp_runtime` / `mcp_toolkit` | MCP server lifecycle and tool injection |
97
+ | `context_aware_tool` | Tool base class that propagates the runtime context (user, team, token) |
97
98
 
98
99
  ---
99
100
 
@@ -141,9 +142,9 @@ or overridden with `--base-url` / `FRED_AGENT_POD_URL`.
141
142
 
142
143
  Every Fred pod uses the same two-file convention:
143
144
 
144
- | File | Purpose |
145
- |------|---------|
146
- | `.env` (path from `ENV_FILE`) | Secrets: API keys, DB URLs, Keycloak credentials |
145
+ | File | Purpose |
146
+ | ---------------------------------------------- | ------------------------------------------------------------------ |
147
+ | `.env` (path from `ENV_FILE`) | Secrets: API keys, DB URLs, Keycloak credentials |
147
148
  | `configuration.yaml` (path from `CONFIG_FILE`) | App settings: port, base URL, LLM routing, observability, security |
148
149
 
149
150
  Minimal `configuration.yaml` for a local pod:
@@ -155,6 +156,7 @@ app:
155
156
  host: "0.0.0.0"
156
157
  port: 8010
157
158
  log_level: "info"
159
+ limit_concurrency: 200
158
160
  metrics_address: "127.0.0.1"
159
161
  metrics_port: 9115
160
162
  kpi_process_metrics_interval_sec: 10
@@ -173,6 +175,10 @@ When `observability.metrics: prometheus` is enabled, `create_agent_app(...)`
173
175
  starts a dedicated Prometheus exporter on `app.metrics_address:app.metrics_port`
174
176
  and restores the shared Fred KPI pipeline, including process and SQL pool KPIs.
175
177
 
178
+ Set `app.limit_concurrency: null` to disable Uvicorn connection limiting, or a
179
+ positive integer to reject excess concurrent HTTP and WebSocket connections
180
+ with `503` before application code runs.
181
+
176
182
  ---
177
183
 
178
184
  ## Installation
@@ -194,6 +200,7 @@ Requires Python 3.12.
194
200
  A minimal pod is three files:
195
201
 
196
202
  **`main.py`**
203
+
197
204
  ```python
198
205
  from fred_runtime.app import create_agent_app, load_agent_pod_config
199
206
  from myapp.registry import REGISTRY
@@ -203,19 +210,27 @@ app = create_agent_app(registry=REGISTRY, config=config)
203
210
  ```
204
211
 
205
212
  **`__main__.py`**
213
+
206
214
  ```python
207
215
  import uvicorn
208
216
  from fred_runtime.app import load_agent_pod_config
209
217
 
210
218
  def main():
211
219
  config = load_agent_pod_config()
212
- uvicorn.run("myapp.main:app", host=config.app.host, port=config.app.port, reload=True)
220
+ uvicorn.run(
221
+ "myapp.main:app",
222
+ host=config.app.host,
223
+ port=config.app.port,
224
+ limit_concurrency=config.app.limit_concurrency,
225
+ reload=True,
226
+ )
213
227
 
214
228
  if __name__ == "__main__":
215
229
  main()
216
230
  ```
217
231
 
218
232
  **`registry.py`**
233
+
219
234
  ```python
220
235
  from fred_sdk.contracts.models import ReActAgentDefinition
221
236
 
@@ -232,11 +247,11 @@ See [fred-samples](https://github.com/ThalesGroup/fred) for a working reference
232
247
 
233
248
  ## Related packages
234
249
 
235
- | Package | PyPI | Role |
236
- |---------|------|------|
237
- | `fred-core` | [pypi](https://pypi.org/project/fred-core/) | Pure utilities — logging, model factories, embeddings, portable observability |
238
- | `fred-sdk` | [pypi](https://pypi.org/project/fred-sdk/) | Agent authoring — ReAct, Graph, tool contracts |
239
- | `fred-runtime` | [pypi](https://pypi.org/project/fred-runtime/) | This package |
250
+ | Package | PyPI | Role |
251
+ | -------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- |
252
+ | `fred-core` | [pypi](https://pypi.org/project/fred-core/) | Pure utilities — logging, model factories, embeddings, portable observability |
253
+ | `fred-sdk` | [pypi](https://pypi.org/project/fred-sdk/) | Agent authoring — ReAct, Graph, tool contracts |
254
+ | `fred-runtime` | [pypi](https://pypi.org/project/fred-runtime/) | This package |
240
255
 
241
256
  ---
242
257
 
@@ -39,7 +39,7 @@ from typing import Any, Literal
39
39
 
40
40
  import yaml
41
41
  from fred_sdk.contracts.models import MCPServerConfiguration
42
- from pydantic import BaseModel, ConfigDict, Field
42
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
43
43
 
44
44
  from .config import AgentPodConfig
45
45
 
@@ -114,6 +114,36 @@ class _McpCatalog(_CatalogFile):
114
114
  version: Literal["v1"] = "v1"
115
115
  servers: list[MCPServerConfiguration] = Field(default_factory=list)
116
116
 
117
+ @model_validator(mode="after")
118
+ def _reject_duplicate_server_ids(self) -> "_McpCatalog":
119
+ """
120
+ Reject duplicate MCP server ids in one catalog.
121
+
122
+ Why this exists:
123
+ - the managed-agent contract now stores per-server config keyed by MCP
124
+ server id, so duplicates would make selection and config resolution
125
+ ambiguous and unsafe
126
+
127
+ How to use it:
128
+ - triggered automatically during `_McpCatalog.model_validate(...)`
129
+
130
+ Example:
131
+ - `load_mcp_catalog("./config/mcp_catalog.yaml")`
132
+ """
133
+
134
+ seen: set[str] = set()
135
+ duplicates: list[str] = []
136
+ for server in self.servers:
137
+ if server.id in seen and server.id not in duplicates:
138
+ duplicates.append(server.id)
139
+ seen.add(server.id)
140
+ if duplicates:
141
+ duplicates_text = ", ".join(repr(server_id) for server_id in duplicates)
142
+ raise ValueError(
143
+ f"Duplicate MCP server id(s) in catalog: {duplicates_text}"
144
+ )
145
+ return self
146
+
117
147
 
118
148
  def _load_yaml_mapping(path: Path) -> dict[str, Any]:
119
149
  """
@@ -849,7 +849,9 @@ class _ResolvedExecutionTarget:
849
849
 
850
850
 
851
851
  def _apply_runtime_tuning(
852
- definition: ReActAgentDefinition | GraphAgentDefinition, tuning: AgentTuning
852
+ definition: ReActAgentDefinition | GraphAgentDefinition,
853
+ tuning: AgentTuning,
854
+ available_mcp_servers: list[MCPServerConfiguration],
853
855
  ) -> ReActAgentDefinition | GraphAgentDefinition:
854
856
  """
855
857
  Overlay persisted business tuning onto one registered agent template.
@@ -863,11 +865,11 @@ def _apply_runtime_tuning(
863
865
  - call after resolving an `agent_instance_id` from control-plane
864
866
 
865
867
  Example:
866
- - `definition = _apply_runtime_tuning(template_definition, resolution.tuning)`
868
+ - `definition = _apply_runtime_tuning(template_definition, resolution.tuning, catalog)`
867
869
  """
868
870
 
869
871
  mcp_servers = tuning.mcp_servers
870
- if tuning.selected_mcp_server_ids:
872
+ if tuning.selected_mcp_server_ids is not None:
871
873
  selected = frozenset(tuning.selected_mcp_server_ids)
872
874
  mcp_servers = [s for s in mcp_servers if s.id in selected]
873
875
 
@@ -886,9 +888,25 @@ def _apply_runtime_tuning(
886
888
  }
887
889
  if isinstance(definition, ReActAgentDefinition):
888
890
  # Also overlay system_prompt_template directly for ReAct runtime compatibility.
891
+ base_system_prompt = str(getattr(definition, "system_prompt_template", ""))
892
+ effective_system_prompt = base_system_prompt
889
893
  system_prompt = tuning.values.get("prompts.system")
890
894
  if isinstance(system_prompt, str) and system_prompt.strip():
891
- update["system_prompt_template"] = system_prompt
895
+ effective_system_prompt = system_prompt
896
+ available_by_id = {server.id: server for server in available_mcp_servers}
897
+ fragments = [
898
+ catalog_entry.agent_instructions.strip()
899
+ for server_ref in mcp_servers
900
+ if (catalog_entry := available_by_id.get(server_ref.id)) is not None
901
+ and isinstance(catalog_entry.agent_instructions, str)
902
+ and catalog_entry.agent_instructions.strip()
903
+ ]
904
+ if fragments:
905
+ effective_system_prompt = f"{effective_system_prompt}\n\n" + "\n\n".join(
906
+ fragments
907
+ )
908
+ if effective_system_prompt != base_system_prompt:
909
+ update["system_prompt_template"] = effective_system_prompt
892
910
  return definition.model_copy(update=update)
893
911
 
894
912
 
@@ -952,6 +970,7 @@ async def _resolve_agent_instance(
952
970
  f"Known agents: {list(registry.keys())}",
953
971
  )
954
972
  if request.inline_tuning:
973
+ available_mcp_servers = _available_mcp_servers_for_definition(definition)
955
974
  definition = _apply_runtime_tuning(
956
975
  definition,
957
976
  AgentTuning(
@@ -962,6 +981,7 @@ async def _resolve_agent_instance(
962
981
  mcp_servers=list(definition.default_mcp_servers),
963
982
  values=request.inline_tuning,
964
983
  ),
984
+ available_mcp_servers,
965
985
  )
966
986
  return _ResolvedExecutionTarget(
967
987
  definition=definition,
@@ -1007,8 +1027,11 @@ async def _resolve_agent_instance(
1007
1027
  f"Resolved template_agent_id '{resolution.template_agent_id}' is not registered in this pod."
1008
1028
  ),
1009
1029
  )
1030
+ available_mcp_servers = _available_mcp_servers_for_definition(definition)
1010
1031
  return _ResolvedExecutionTarget(
1011
- definition=_apply_runtime_tuning(definition, resolution.tuning),
1032
+ definition=_apply_runtime_tuning(
1033
+ definition, resolution.tuning, available_mcp_servers
1034
+ ),
1012
1035
  effective_agent_id=resolution.agent_instance_id,
1013
1036
  team_id=resolution.owner_team_id,
1014
1037
  )
@@ -1473,6 +1496,7 @@ def _build_eval_trace(
1473
1496
  agent_id: str,
1474
1497
  session_id: str,
1475
1498
  turn_start: float,
1499
+ agent_tags: tuple[str, ...] = (),
1476
1500
  ) -> EvalTrace:
1477
1501
  outcome = _parse_turn_outcome(payloads, turn_start)
1478
1502
  steps: list[EvalStep] = []
@@ -1531,6 +1555,7 @@ def _build_eval_trace(
1531
1555
  return EvalTrace(
1532
1556
  session_id=session_id,
1533
1557
  agent_id=agent_id,
1558
+ agent_tags=agent_tags,
1534
1559
  input=input_text,
1535
1560
  output=outcome.final_content,
1536
1561
  error=error,
@@ -1825,18 +1850,6 @@ async def _iterate_runtime_event_payloads(
1825
1850
  registry=registry,
1826
1851
  access_token=access_token,
1827
1852
  )
1828
- if isinstance(definition, GraphAgentDefinition):
1829
- runtime: ReActRuntime | GraphRuntime = GraphRuntime(
1830
- definition=definition,
1831
- services=services,
1832
- )
1833
- else:
1834
- runtime = ReActRuntime(
1835
- definition=definition,
1836
- services=services,
1837
- )
1838
- runtime.bind(binding)
1839
-
1840
1853
  # session_id drives LangGraph checkpointing: the agent resumes its graph
1841
1854
  # state on every turn. Falls back to request_id for one-shot calls so
1842
1855
  # LangGraph's checkpointer invariant (thread_id required internally) is met.
@@ -1847,10 +1860,16 @@ async def _iterate_runtime_event_payloads(
1847
1860
  invocation_turns=getattr(request, "invocation_turns", ()),
1848
1861
  )
1849
1862
 
1863
+ runtime: ReActRuntime | GraphRuntime | None = None
1850
1864
  try:
1851
- await runtime.activate()
1852
- executor = await runtime.get_executor()
1853
1865
  if isinstance(definition, GraphAgentDefinition):
1866
+ runtime = GraphRuntime(
1867
+ definition=definition,
1868
+ services=services,
1869
+ )
1870
+ runtime.bind(binding)
1871
+ await runtime.activate()
1872
+ executor = await runtime.get_executor()
1854
1873
  # Graph agents receive their typed input schema; the agent's
1855
1874
  # build_turn_state() maps it to graph state before the first node runs.
1856
1875
  # The standard contract is a single "message" field in the input schema.
@@ -1863,12 +1882,25 @@ async def _iterate_runtime_event_payloads(
1863
1882
  graph_input = input_cls.model_validate(
1864
1883
  {"message": request.message or ""}
1865
1884
  )
1866
- executor_input: ReActInput | object = graph_input
1885
+ async for event in executor.stream(graph_input, execution_config):
1886
+ payload = event.model_dump(mode="json")
1887
+ if not isinstance(payload, dict):
1888
+ raise RuntimeError(
1889
+ "RuntimeEvent payload must serialize to a JSON object."
1890
+ )
1891
+ yield payload
1867
1892
  else:
1893
+ runtime = ReActRuntime(
1894
+ definition=definition,
1895
+ services=services,
1896
+ )
1897
+ runtime.bind(binding)
1898
+ await runtime.activate()
1899
+ executor = await runtime.get_executor()
1868
1900
  # On HITL resume, messages are ignored by the codec — the graph
1869
1901
  # resumes from its checkpointed interrupt via Command(resume=...).
1870
1902
  # On a normal turn, the user message is the only input.
1871
- executor_input = ReActInput(
1903
+ react_input = ReActInput(
1872
1904
  messages=(
1873
1905
  ()
1874
1906
  if request.resume_payload is not None
@@ -1879,20 +1911,21 @@ async def _iterate_runtime_event_payloads(
1879
1911
  )
1880
1912
  ),
1881
1913
  )
1882
- async for event in executor.stream(executor_input, execution_config):
1883
- payload = event.model_dump(mode="json")
1884
- if not isinstance(payload, dict):
1885
- raise RuntimeError(
1886
- "RuntimeEvent payload must serialize to a JSON object."
1887
- )
1888
- yield payload
1914
+ async for event in executor.stream(react_input, execution_config):
1915
+ payload = event.model_dump(mode="json")
1916
+ if not isinstance(payload, dict):
1917
+ raise RuntimeError(
1918
+ "RuntimeEvent payload must serialize to a JSON object."
1919
+ )
1920
+ yield payload
1889
1921
  except Exception as exc:
1890
1922
  logger.exception(
1891
1923
  "[fred-runtime] agent execution error agent_id=%s", definition.agent_id
1892
1924
  )
1893
1925
  yield RuntimeErrorEvent(message=str(exc)).model_dump(mode="json")
1894
1926
  finally:
1895
- await runtime.dispose()
1927
+ if runtime is not None:
1928
+ await runtime.dispose()
1896
1929
 
1897
1930
 
1898
1931
  def _terminal_execute_payload(
@@ -2632,6 +2665,7 @@ def _build_agent_router(
2632
2665
  payloads=payloads,
2633
2666
  input_text=request.input or "",
2634
2667
  agent_id=target.definition.agent_id,
2668
+ agent_tags=target.definition.tags,
2635
2669
  session_id=eval_session_id,
2636
2670
  turn_start=turn_start,
2637
2671
  )
@@ -110,6 +110,15 @@ class PodAppConfig(BaseModel):
110
110
  host: str = "127.0.0.1"
111
111
  port: int = 8000
112
112
  log_level: str = "info"
113
+ limit_concurrency: int | None = Field(
114
+ default=None,
115
+ ge=1,
116
+ description=(
117
+ "Optional maximum number of concurrent HTTP or WebSocket "
118
+ "connections accepted by Uvicorn. Leave unset to disable the "
119
+ "limit."
120
+ ),
121
+ )
113
122
  gcu_version: str | None = None
114
123
  metrics_address: str = "127.0.0.1"
115
124
  metrics_port: int = 9000
@@ -36,7 +36,7 @@ _COMMANDS: tuple[str, ...] = (
36
36
  "/whoami",
37
37
  )
38
38
 
39
- # Scenario keywords for fred.test.assistant — used for /run tab-completion.
39
+ # Scenario keywords for fred.github.test_assistant — used for /run tab-completion.
40
40
  _TEST_ASSISTANT_SCENARIOS: tuple[str, ...] = (
41
41
  "echo",
42
42
  "error",