databricks-agentbricks 0.2.0__py3-none-any.whl

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 (159) hide show
  1. databricks_agentbricks/__init__.py +1 -0
  2. databricks_agentbricks/_group.py +217 -0
  3. databricks_agentbricks/agent_project.py +657 -0
  4. databricks_agentbricks/app_resources.py +139 -0
  5. databricks_agentbricks/cli/__init__.py +12 -0
  6. databricks_agentbricks/cli/app.py +108 -0
  7. databricks_agentbricks/cli/app_auth.py +198 -0
  8. databricks_agentbricks/cli/auth.py +142 -0
  9. databricks_agentbricks/cli/deploy.py +954 -0
  10. databricks_agentbricks/cli/dev.py +310 -0
  11. databricks_agentbricks/cli/endpoint.py +145 -0
  12. databricks_agentbricks/cli/endpoint_examples.py +33 -0
  13. databricks_agentbricks/cli/endpoint_output.py +53 -0
  14. databricks_agentbricks/cli/endpoint_request.py +71 -0
  15. databricks_agentbricks/cli/endpoint_transport.py +148 -0
  16. databricks_agentbricks/cli/help.py +467 -0
  17. databricks_agentbricks/cli/init.py +496 -0
  18. databricks_agentbricks/cli/mcp.py +75 -0
  19. databricks_agentbricks/cli/memory.py +520 -0
  20. databricks_agentbricks/cli/sandbox.py +823 -0
  21. databricks_agentbricks/cli/sessions.py +588 -0
  22. databricks_agentbricks/cli/tools.py +456 -0
  23. databricks_agentbricks/cli/tracing.py +724 -0
  24. databricks_agentbricks/databricks_cli.py +38 -0
  25. databricks_agentbricks/errors.py +114 -0
  26. databricks_agentbricks/lakebase_runtime_store.py +107 -0
  27. databricks_agentbricks/legacy_lakebase_runtime_store.py +74 -0
  28. databricks_agentbricks/project_config.py +157 -0
  29. databricks_agentbricks/project_types.py +55 -0
  30. databricks_agentbricks/py.typed +0 -0
  31. databricks_agentbricks/render.py +349 -0
  32. databricks_agentbricks/templates/agent-bricks-migrate/SKILL.md +101 -0
  33. databricks_agentbricks/templates/agent-langgraph/.env.example +36 -0
  34. databricks_agentbricks/templates/agent-langgraph/.gitignore +199 -0
  35. databricks_agentbricks/templates/agent-langgraph/AGENTKIT_CONTRACT.md +95 -0
  36. databricks_agentbricks/templates/agent-langgraph/AGENTS.md +31 -0
  37. databricks_agentbricks/templates/agent-langgraph/CLAUDE.md +1 -0
  38. databricks_agentbricks/templates/agent-langgraph/README.md +141 -0
  39. databricks_agentbricks/templates/agent-langgraph/agent/__init__.py +0 -0
  40. databricks_agentbricks/templates/agent-langgraph/agent/agent.py +177 -0
  41. databricks_agentbricks/templates/agent-langgraph/agent/mcps.py +27 -0
  42. databricks_agentbricks/templates/agent-langgraph/agent/tools/__init__.py +24 -0
  43. databricks_agentbricks/templates/agent-langgraph/agent/tools/sample_tool.py +15 -0
  44. databricks_agentbricks/templates/agent-langgraph/agent/tools/send_message.py +17 -0
  45. databricks_agentbricks/templates/agent-langgraph/app.yaml +15 -0
  46. databricks_agentbricks/templates/agent-langgraph/pyproject.toml +42 -0
  47. databricks_agentbricks/templates/agent-langgraph/runtime/__init__.py +0 -0
  48. databricks_agentbricks/templates/agent-langgraph/runtime/adapter.py +144 -0
  49. databricks_agentbricks/templates/agent-langgraph/runtime/main.py +25 -0
  50. databricks_agentbricks/templates/agent-langgraph/tests/__init__.py +0 -0
  51. databricks_agentbricks/templates/agent-langgraph/tests/test_agent.py +211 -0
  52. databricks_agentbricks/templates/agent-langgraph/tests/test_runtime.py +19 -0
  53. databricks_agentbricks/templates/agent-langgraph/tests/test_workspace.py +28 -0
  54. databricks_agentbricks/templates/agent-openai/.env.example +37 -0
  55. databricks_agentbricks/templates/agent-openai/.gitignore +199 -0
  56. databricks_agentbricks/templates/agent-openai/AGENTKIT_CONTRACT.md +107 -0
  57. databricks_agentbricks/templates/agent-openai/AGENTS.md +76 -0
  58. databricks_agentbricks/templates/agent-openai/CLAUDE.md +1 -0
  59. databricks_agentbricks/templates/agent-openai/README.md +143 -0
  60. databricks_agentbricks/templates/agent-openai/agent/__init__.py +0 -0
  61. databricks_agentbricks/templates/agent-openai/agent/agent.py +205 -0
  62. databricks_agentbricks/templates/agent-openai/agent/mcps.py +28 -0
  63. databricks_agentbricks/templates/agent-openai/agent/tools/__init__.py +24 -0
  64. databricks_agentbricks/templates/agent-openai/agent/tools/sample_tool.py +15 -0
  65. databricks_agentbricks/templates/agent-openai/agent/tools/send_message.py +18 -0
  66. databricks_agentbricks/templates/agent-openai/app.yaml +15 -0
  67. databricks_agentbricks/templates/agent-openai/pyproject.toml +42 -0
  68. databricks_agentbricks/templates/agent-openai/runtime/__init__.py +0 -0
  69. databricks_agentbricks/templates/agent-openai/runtime/adapter.py +188 -0
  70. databricks_agentbricks/templates/agent-openai/runtime/main.py +25 -0
  71. databricks_agentbricks/templates/agent-openai/tests/__init__.py +0 -0
  72. databricks_agentbricks/templates/agent-openai/tests/test_agent.py +326 -0
  73. databricks_agentbricks/templates/agent-openai/tests/test_runtime.py +19 -0
  74. databricks_agentbricks/templates/agent-openai/tests/test_workspace.py +28 -0
  75. databricks_agentbricks/templates/custom-agent-langgraph/.env.example +1 -0
  76. databricks_agentbricks/templates/custom-agent-langgraph/.gitignore +11 -0
  77. databricks_agentbricks/templates/custom-agent-langgraph/README.md +22 -0
  78. databricks_agentbricks/templates/custom-agent-langgraph/agent/__init__.py +0 -0
  79. databricks_agentbricks/templates/custom-agent-langgraph/agent/agent.py +27 -0
  80. databricks_agentbricks/templates/custom-agent-langgraph/app.yaml +1 -0
  81. databricks_agentbricks/templates/custom-agent-langgraph/pyproject.toml +38 -0
  82. databricks_agentbricks/templates/custom-agent-langgraph/runtime/__init__.py +0 -0
  83. databricks_agentbricks/templates/custom-agent-langgraph/runtime/main.py +31 -0
  84. databricks_agentbricks/templates/custom-agent-langgraph/tests/test_runtime.py +29 -0
  85. databricks_agentbricks/templates/custom-agent-openai/.env.example +1 -0
  86. databricks_agentbricks/templates/custom-agent-openai/.gitignore +11 -0
  87. databricks_agentbricks/templates/custom-agent-openai/README.md +22 -0
  88. databricks_agentbricks/templates/custom-agent-openai/agent/__init__.py +0 -0
  89. databricks_agentbricks/templates/custom-agent-openai/agent/agent.py +29 -0
  90. databricks_agentbricks/templates/custom-agent-openai/app.yaml +1 -0
  91. databricks_agentbricks/templates/custom-agent-openai/pyproject.toml +38 -0
  92. databricks_agentbricks/templates/custom-agent-openai/runtime/__init__.py +0 -0
  93. databricks_agentbricks/templates/custom-agent-openai/runtime/main.py +31 -0
  94. databricks_agentbricks/templates/custom-agent-openai/tests/test_runtime.py +29 -0
  95. databricks_agentbricks/templates/sandbox_mcp.py +39 -0
  96. databricks_agentbricks/templates/sandbox_mcp_langgraph.py +39 -0
  97. databricks_agentbricks/templates/ui/agent-langgraph/CHAT_APP.md +49 -0
  98. databricks_agentbricks/templates/ui/agent-langgraph/runtime/main.py +26 -0
  99. databricks_agentbricks/templates/ui/agent-langgraph/runtime/ui.py +528 -0
  100. databricks_agentbricks/templates/ui/agent-langgraph/tests/test_demo_ui.py +542 -0
  101. databricks_agentbricks/templates/ui/agent-langgraph/ui/agentbricks-mark.png +0 -0
  102. databricks_agentbricks/templates/ui/agent-langgraph/ui/app.js +1464 -0
  103. databricks_agentbricks/templates/ui/agent-langgraph/ui/index.html +281 -0
  104. databricks_agentbricks/templates/ui/agent-langgraph/ui/styles.css +1913 -0
  105. databricks_agentbricks/templates/ui/agent-openai/CHAT_APP.md +54 -0
  106. databricks_agentbricks/templates/ui/agent-openai/runtime/main.py +26 -0
  107. databricks_agentbricks/templates/ui/agent-openai/runtime/ui.py +521 -0
  108. databricks_agentbricks/templates/ui/agent-openai/tests/test_demo_ui.py +512 -0
  109. databricks_agentbricks/templates/ui/agent-openai/ui/agentbricks-mark.png +0 -0
  110. databricks_agentbricks/templates/ui/agent-openai/ui/app.js +1428 -0
  111. databricks_agentbricks/templates/ui/agent-openai/ui/index.html +281 -0
  112. databricks_agentbricks/templates/ui/agent-openai/ui/styles.css +1913 -0
  113. databricks_agentbricks/theme.py +72 -0
  114. databricks_agentbricks-0.2.0.dist-info/METADATA +897 -0
  115. databricks_agentbricks-0.2.0.dist-info/RECORD +159 -0
  116. databricks_agentbricks-0.2.0.dist-info/WHEEL +4 -0
  117. databricks_agentbricks-0.2.0.dist-info/entry_points.txt +2 -0
  118. databricks_agentbricks-0.2.0.dist-info/licenses/NOTICE +39 -0
  119. databricks_agentkit/__init__.py +67 -0
  120. databricks_agentkit/_api_client.py +673 -0
  121. databricks_agentkit/_pagination.py +25 -0
  122. databricks_agentkit/client.py +31 -0
  123. databricks_agentkit/langgraph/__init__.py +98 -0
  124. databricks_agentkit/langgraph/genie.py +50 -0
  125. databricks_agentkit/langgraph/mcp.py +216 -0
  126. databricks_agentkit/langgraph/memory.py +67 -0
  127. databricks_agentkit/langgraph/session_store.py +485 -0
  128. databricks_agentkit/memory_store.py +371 -0
  129. databricks_agentkit/models.py +330 -0
  130. databricks_agentkit/openai/__init__.py +93 -0
  131. databricks_agentkit/openai/genie.py +50 -0
  132. databricks_agentkit/openai/mcp.py +189 -0
  133. databricks_agentkit/openai/memory.py +67 -0
  134. databricks_agentkit/openai/sessions.py +157 -0
  135. databricks_agentkit/py.typed +0 -0
  136. databricks_agentkit/runtime/ARCHITECTURE.md +64 -0
  137. databricks_agentkit/runtime/README.md +184 -0
  138. databricks_agentkit/runtime/__init__.py +69 -0
  139. databricks_agentkit/runtime/app.py +316 -0
  140. databricks_agentkit/runtime/auth.py +149 -0
  141. databricks_agentkit/runtime/durability/__init__.py +1 -0
  142. databricks_agentkit/runtime/durability/execution.py +133 -0
  143. databricks_agentkit/runtime/durability/heartbeat.py +64 -0
  144. databricks_agentkit/runtime/durability/lakebase_runtime_store.py +648 -0
  145. databricks_agentkit/runtime/durability/recovery.py +70 -0
  146. databricks_agentkit/runtime/durability/store.py +33 -0
  147. databricks_agentkit/runtime/execution.py +160 -0
  148. databricks_agentkit/runtime/genie.py +224 -0
  149. databricks_agentkit/runtime/mcp_auth.py +114 -0
  150. databricks_agentkit/runtime/model_services.py +90 -0
  151. databricks_agentkit/runtime/runtime.py +273 -0
  152. databricks_agentkit/runtime/session_store_client.py +120 -0
  153. databricks_agentkit/runtime/store.py +287 -0
  154. databricks_agentkit/runtime/tool_manifest.py +223 -0
  155. databricks_agentkit/runtime/tracing.py +71 -0
  156. databricks_agentkit/runtime/types.py +118 -0
  157. databricks_agentkit/runtime/workspace.py +27 -0
  158. databricks_agentkit/session_store.py +434 -0
  159. databricks_agentkit/timefmt.py +85 -0
@@ -0,0 +1,93 @@
1
+ """OpenAI Agents SDK adapter for running an agent on Databricks (installed via ``databricks-agentbricks[openai]``).
2
+
3
+ Composable pieces you drop into an existing OpenAI Agents SDK agent — a session store, MCP servers
4
+ declared in ``agent.toml``, long-term memory tools, and MLflow tracing. Each maps onto a slot the
5
+ Agents SDK already has, so migrating an existing agent is a graft, not a rewrite::
6
+
7
+ from databricks_agentkit.openai import (
8
+ session_store,
9
+ mcp_servers,
10
+ memory_tools,
11
+ configure_tracing,
12
+ )
13
+
14
+ configure_tracing()
15
+ agent = Agent(
16
+ name="Agent",
17
+ model="databricks-gpt-5-2",
18
+ tools=[*your_tools, *memory_tools()],
19
+ mcp_servers=await mcp_servers(), # your agent.toml servers + any you pass
20
+ )
21
+ result = await Runner.run(agent, messages, session=session_store(session_id))
22
+
23
+ These need the agent stack (openai-agents, databricks-openai, mlflow), so they sit behind the
24
+ ``[openai]`` extra to keep a plain ``databricks-agentbricks`` installation independent of agent frameworks.
25
+
26
+ ``__all__`` is the curated surface. Other entry points (``DatabricksSessionStore``) are reachable by
27
+ their submodule paths but not re-exported here.
28
+ """
29
+
30
+ from typing import TYPE_CHECKING
31
+
32
+ if TYPE_CHECKING:
33
+ from databricks_agentkit.openai.genie import genie_tools
34
+ from databricks_agentkit.openai.mcp import mcp_servers
35
+ from databricks_agentkit.openai.memory import memory_tools
36
+ from databricks_agentkit.openai.sessions import session_store
37
+ from databricks_agentkit.runtime import (
38
+ start_trace,
39
+ workspace_client,
40
+ workspace_headers,
41
+ )
42
+
43
+
44
+ def configure_tracing() -> None:
45
+ """Enable MLflow tracing with OpenAI autologging. Call once at startup.
46
+
47
+ Safe to call unconditionally — tracing turns on only when the MLflow destination and experiment
48
+ are configured in the environment (see :func:`databricks_agentkit.runtime.configure_tracing`).
49
+ """
50
+ import mlflow
51
+
52
+ from databricks_agentkit.runtime import configure_tracing as _configure_tracing
53
+
54
+ _configure_tracing(autolog=mlflow.openai.autolog)
55
+
56
+
57
+ __all__ = [
58
+ "genie_tools",
59
+ # MCP servers from agent.toml (plus any you pass) — hand them to Agent(mcp_servers=...).
60
+ "mcp_servers",
61
+ # Long-term memory tools (opt-in via AGENT_MEMORY_STORE) — add to your tool list.
62
+ "memory_tools",
63
+ # Session persistence — pass session_store(session_id) to Runner.run(session=...).
64
+ "session_store",
65
+ # MLflow tracing (OpenAI autolog bound in) — call configure_tracing() once at startup.
66
+ "configure_tracing",
67
+ # Wrap each invocation in start_trace() so a trace is recorded (pass session_id= to tag it).
68
+ "start_trace",
69
+ # Workspace SDK client construction.
70
+ "workspace_client",
71
+ "workspace_headers",
72
+ ]
73
+
74
+ # Re-exports resolved lazily (PEP 562) so importing one submodule (e.g. ``.mcp``) does not eagerly
75
+ # pull in the others' dependencies. ``configure_tracing`` is defined above (binds OpenAI autolog).
76
+ _MODULE_BY_NAME = {
77
+ "genie_tools": "databricks_agentkit.openai.genie",
78
+ "mcp_servers": "databricks_agentkit.openai.mcp",
79
+ "memory_tools": "databricks_agentkit.openai.memory",
80
+ "session_store": "databricks_agentkit.openai.sessions",
81
+ "start_trace": "databricks_agentkit.runtime",
82
+ "workspace_client": "databricks_agentkit.runtime",
83
+ "workspace_headers": "databricks_agentkit.runtime",
84
+ }
85
+
86
+
87
+ def __getattr__(name: str) -> object:
88
+ module = _MODULE_BY_NAME.get(name)
89
+ if module is None:
90
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
91
+ import importlib
92
+
93
+ return getattr(importlib.import_module(module), name)
@@ -0,0 +1,50 @@
1
+ """Native Genie Agent tools for OpenAI Agents, configured through ``agent.toml``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Callable
7
+ from typing import TYPE_CHECKING
8
+
9
+ from agents import FunctionTool, function_tool
10
+
11
+ from databricks_agentkit.runtime.auth import AuthError
12
+ from databricks_agentkit.runtime.genie import GenieAgent
13
+ from databricks_agentkit.runtime.tool_manifest import load_tools
14
+
15
+ if TYPE_CHECKING:
16
+ from databricks.sdk import WorkspaceClient
17
+
18
+
19
+ def genie_tools(
20
+ *, workspace_client_for: Callable[[str], WorkspaceClient] | None = None
21
+ ) -> list[FunctionTool]:
22
+ """Build ask, poll and query-result tools for each configured Genie Agent.
23
+
24
+ Space IDs are bound from the manifest, not exposed as model arguments. Construction performs
25
+ no authentication or network requests; an agent with no Genie Agent bindings gets no tools.
26
+ """
27
+ tools = []
28
+ for record in load_tools(expected_framework="openai"):
29
+ if record.kind != "genie_agent":
30
+ continue
31
+ if (
32
+ record.auth == "user"
33
+ and workspace_client_for is None
34
+ and os.getenv("DATABRICKS_APP_NAME")
35
+ ):
36
+ raise AuthError(
37
+ "MCP_USER_AUTHORIZATION_MISSING",
38
+ "This deployed Genie integration requires request-user authorization.",
39
+ integration_id=record.id,
40
+ )
41
+ agent = GenieAgent(
42
+ record.space_id or "",
43
+ auth=record.auth or "app",
44
+ workspace_client_for=workspace_client_for,
45
+ )
46
+ tools.extend(
47
+ function_tool(method, name_override=f"{record.id}_{method.__name__}")
48
+ for method in (agent.ask, agent.poll, agent.query_result)
49
+ )
50
+ return tools
@@ -0,0 +1,189 @@
1
+ """Build MCP servers for the agent from the ones declared in ``agent.toml`` (plus any the agent adds).
2
+
3
+ ``mcp_servers()`` is the entry point: it reads the MCP servers declared in ``agent.toml``
4
+ (sandbox/mcp + uc_function) and returns Agents SDK ``McpServer`` objects, with sandbox downscoping
5
+ applied. Hand them to ``Agent(mcp_servers=...)``; the Agents SDK connects and lists their tools
6
+ lazily inside ``Runner.run``. An agent with its own hand-built servers passes them as
7
+ ``extra_servers``; leaving ``agent.toml`` empty simply yields no declared servers. Typical use::
8
+
9
+ servers = await mcp_servers() # just the agent.toml servers
10
+ servers = await mcp_servers(build_mcp_servers()) # agent.toml servers + the agent's own
11
+
12
+ Unlike a fetch-once tool list, these are connection objects: open them for the life of the request
13
+ (e.g. via ``AsyncExitStack``), because the SDK lists each server's tools only when the run needs them.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any
19
+
20
+ from databricks_openai.agents import McpServer
21
+
22
+ from databricks_agentkit.runtime import mcp_auth
23
+ from databricks_agentkit.runtime.auth import AuthError
24
+ from databricks_agentkit.runtime.tool_manifest import (
25
+ ToolRecord,
26
+ downscope_wire,
27
+ load_tools,
28
+ )
29
+ from databricks_agentkit.runtime.workspace import workspace_client, workspace_headers
30
+
31
+ _FRAMEWORK = "openai"
32
+ _auth_error = mcp_auth.mcp_auth_error
33
+ _tool_error = mcp_auth.mcp_tool_error
34
+
35
+
36
+ class _ConfiguredMcpServer(McpServer):
37
+ """Keep request-user failures out of SDK best-effort discovery and tool results."""
38
+
39
+ def __init__(self, *args: Any, request_user: bool, **kwargs: Any) -> None:
40
+ self._agentbricks_request_user = request_user
41
+ if request_user:
42
+ kwargs["failure_error_function"] = self._raise_tool_error
43
+ super().__init__(*args, **kwargs)
44
+
45
+ def _raise_tool_error(self, context: Any, error: Exception) -> str:
46
+ raise _auth_error(error, self.name) or AuthError(
47
+ "MCP_TOOL_FAILED", "The configured MCP tool failed.", 502, self.name
48
+ ) from None
49
+
50
+ async def connect(self):
51
+ if not self._agentbricks_request_user:
52
+ return await super().connect()
53
+ try:
54
+ return await super().connect()
55
+ except Exception as error:
56
+ raise _auth_error(error, self.name) or AuthError(
57
+ "MCP_TOOL_FAILED",
58
+ "Could not connect to the configured MCP service.",
59
+ 502,
60
+ self.name,
61
+ ) from None
62
+
63
+ async def list_tools(self, *args, **kwargs):
64
+ if not self._agentbricks_request_user:
65
+ return await super().list_tools(*args, **kwargs)
66
+ try:
67
+ return await super().list_tools(*args, **kwargs)
68
+ except Exception as error:
69
+ raise _auth_error(error, self.name) or AuthError(
70
+ "MCP_TOOL_FAILED", "Could not discover configured MCP tools.", 502, self.name
71
+ ) from None
72
+
73
+ async def call_tool(self, tool_name, arguments, **kwargs):
74
+ if not self._agentbricks_request_user:
75
+ return await super().call_tool(tool_name, arguments, **kwargs)
76
+ try:
77
+ call = getattr(McpServer.call_tool, "__wrapped__", McpServer.call_tool)
78
+ result = await call(self, tool_name, arguments, **kwargs)
79
+ except Exception as error:
80
+ raise _auth_error(error, self.name) or AuthError(
81
+ "MCP_TOOL_FAILED", "The configured MCP tool failed.", 502, self.name
82
+ ) from None
83
+ if getattr(result, "isError", False) and (error := _tool_error(result, self.name)):
84
+ raise error
85
+ return result
86
+
87
+
88
+ class _DownscopedMcpServer(_ConfiguredMcpServer):
89
+ """An ``McpServer`` that injects a sandbox downscope into every ``call_tool``.
90
+
91
+ The Databricks sandbox MCP applies the downscope from the call's ``_meta``; the Agents SDK does
92
+ not surface a per-call hook, so bind the manifest's downscope to the server and add it on each
93
+ invocation. Only sandbox bindings need this — plain MCP / UC-function servers use the base class.
94
+ """
95
+
96
+ def __init__(self, *args: Any, downscope: dict[str, Any], **kwargs: Any) -> None:
97
+ super().__init__(*args, **kwargs)
98
+ self._downscope = downscope
99
+
100
+ async def call_tool(self, tool_name, arguments, **kwargs):
101
+ meta = {**(kwargs.pop("meta", None) or {}), "downscope": self._downscope}
102
+ return await super().call_tool(tool_name, arguments, meta=meta, **kwargs)
103
+
104
+
105
+ def _server_from_tool(
106
+ tool: ToolRecord,
107
+ *,
108
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
109
+ ) -> McpServer | None:
110
+ if tool.kind not in {"sandbox", "mcp", "uc_function", "genie_one"}:
111
+ return None
112
+ mode = tool.auth or "app"
113
+ client = mcp_auth.resolve_mcp_workspace_client(
114
+ mode, tool.id, workspace_client_for, workspace_client
115
+ )
116
+ host = client.config.host.rstrip("/")
117
+ if tool.kind in {"sandbox", "mcp", "genie_one"}:
118
+ url = (
119
+ f"{host}/api/2.0/mcp/genie"
120
+ if tool.kind == "genie_one"
121
+ else f"{host}/ai-gateway/mcp-services/{tool.service}"
122
+ )
123
+ if tool.kind == "sandbox":
124
+ return _DownscopedMcpServer(
125
+ url=url,
126
+ name=tool.id,
127
+ workspace_client=client,
128
+ timeout=120.0,
129
+ downscope=downscope_wire(tool),
130
+ request_user=mode == "user",
131
+ )
132
+ if tool.kind == "genie_one":
133
+ return _ConfiguredMcpServer(
134
+ name=tool.id,
135
+ workspace_client=client,
136
+ timeout=120.0,
137
+ params={"url": url, "headers": workspace_headers()},
138
+ request_user=mode == "user",
139
+ )
140
+ return _ConfiguredMcpServer(
141
+ url=url,
142
+ name=tool.id,
143
+ workspace_client=client,
144
+ timeout=120.0,
145
+ request_user=mode == "user",
146
+ )
147
+ if tool.kind == "uc_function":
148
+ catalog, schema, function_name = (tool.function or "").split(".")
149
+ return _ConfiguredMcpServer.from_uc_function(
150
+ catalog=catalog,
151
+ schema=schema,
152
+ function_name=function_name,
153
+ name=tool.id,
154
+ workspace_client=client,
155
+ timeout=120.0,
156
+ request_user=mode == "user",
157
+ )
158
+ return None
159
+
160
+
161
+ def _declared_servers(
162
+ *,
163
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
164
+ ) -> list[McpServer]:
165
+ """The MCP servers declared in the agent's ``agent.toml`` (may be empty)."""
166
+ tools = load_tools(expected_framework=_FRAMEWORK)
167
+ return [
168
+ server
169
+ for tool in tools
170
+ if (server := _server_from_tool(tool, workspace_client_for=workspace_client_for))
171
+ is not None
172
+ ]
173
+
174
+
175
+ async def mcp_servers(
176
+ extra_servers: list[McpServer] | None = None,
177
+ *,
178
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
179
+ ) -> list[McpServer]:
180
+ """Build the agent's configured servers with request identity and sandbox downscoping.
181
+
182
+ Includes the MCP servers declared in ``agent.toml``; pass ``extra_servers`` to add servers the
183
+ agent builds itself. Request-user failures propagate. App and extra servers retain the SDK's
184
+ best-effort behavior; the request resolver is never forwarded to extra servers.
185
+ """
186
+ return [
187
+ *_declared_servers(workspace_client_for=workspace_client_for),
188
+ *(extra_servers or []),
189
+ ]
@@ -0,0 +1,67 @@
1
+ """Long-term memory tools — opt-in, gated on ``AGENT_MEMORY_STORE``.
2
+
3
+ Unlike the session store (short-term transcript for one conversation), long-term memory is exposed
4
+ to the model as two tools — ``remember`` and ``recall`` — over the Databricks managed memory store's
5
+ ``2.0/agents`` entries API. Facts persist across conversations.
6
+
7
+ ``memory_tools(actor)`` returns the tools when ``AGENT_MEMORY_STORE`` is set, else an empty list, so
8
+ the model never sees them when memory is unconfigured. The agent composes them into its tool list.
9
+ This is a stand-in for a future ``databricks-openai`` memory helper — when the SDK provides one, swap
10
+ the import in ``agent.py`` and delete this file.
11
+
12
+ Memory entries are per-actor. ``actor`` is the identity whose memory these tools read and write — the
13
+ caller (the agent server) supplies it, typically the signed-in user, so each user gets their own
14
+ long-term memory. It is **closed over**, not a tool argument: the model only ever sees ``remember``'s
15
+ and ``recall``'s own parameters and cannot set or spoof the actor. Pass a fixed value for one shared
16
+ memory (e.g. a team knowledge base).
17
+ """
18
+
19
+ from agents import FunctionTool, function_tool
20
+
21
+ from databricks_agentkit.runtime.workspace import workspace_client
22
+
23
+ _AGENTS_API = "/api/2.0/agents"
24
+
25
+
26
+ def _api():
27
+ # Build the client lazily (needs workspace auth) so importing this module stays cheap.
28
+ return workspace_client().api_client
29
+
30
+
31
+ def memory_tools(actor: str, store: str | None = None) -> list[FunctionTool]:
32
+ """The long-term-memory tools for ``actor`` when a memory store is configured, else none.
33
+
34
+ The store resolves ``store`` arg → ``AGENT_MEMORY_STORE`` env → the ``[memory_store]`` binding in
35
+ agent.toml (`ab memory bind`) → none (no tools). ``actor`` partitions the store; it is captured
36
+ in the tools' closures (not exposed to the model). Call this per request with the identity whose
37
+ memory to use (e.g. the signed-in user).
38
+ """
39
+ from databricks_agentkit.runtime.tool_manifest import resolve_memory_store
40
+
41
+ store = resolve_memory_store(store)
42
+ if not store:
43
+ return []
44
+ store_path = f"{_AGENTS_API}/memory-stores/{store}"
45
+
46
+ @function_tool
47
+ def remember(fact: str, topic: str) -> str:
48
+ """Persist a durable fact about the user in long-term memory."""
49
+ _api().do(
50
+ "POST",
51
+ f"{store_path}/entries",
52
+ body={"actor_id": actor, "path": f"/{topic}/{fact[:8]}.md", "content": fact},
53
+ )
54
+ return "stored"
55
+
56
+ @function_tool
57
+ def recall(query: str) -> str:
58
+ """Search the user's long-term memory for facts relevant to the query."""
59
+ data = _api().do(
60
+ "POST",
61
+ f"{store_path}/entries:search",
62
+ body={"actor_id": actor, "query": query, "limit": 5},
63
+ )
64
+ entries = data.get("managed_memory_entries") or []
65
+ return "\n".join(f"- {e.get('content')}" for e in entries) or "No relevant memories."
66
+
67
+ return [remember, recall]
@@ -0,0 +1,157 @@
1
+ """Conversation session store for the agent.
2
+
3
+ The OpenAI Agents SDK persists conversation state through a **Session** — an object with
4
+ ``get_items``/``add_items``/``pop_item``/``clear_session`` over a list of Responses input items.
5
+ ``Runner.run(..., session=session_store(session_id))`` reads prior turns from it and appends the new
6
+ ones. ``session_store(session_id)`` returns the Session for a given conversation.
7
+
8
+ Default (no config): an in-process ``SQLiteSession`` backed by ``:memory:``, cached per session id —
9
+ multi-turn history is preserved within a single running process, no database. It does NOT survive
10
+ restarts or span replicas.
11
+
12
+ Durable (``AGENT_SESSION_STORE`` set): a ``DatabricksSessionStore``. Instead of a database the app
13
+ connects to directly, it stores each Responses item as one **session item** through the managed
14
+ Session Store REST API (over RPCs only — no Lakebase/Postgres connection), so the transcript is
15
+ durable across restarts and replicas. Setting the env var is the only change; the agent code is
16
+ identical.
17
+
18
+ Note — unlike the LangGraph checkpointer, a Session persists only the conversation transcript, not
19
+ paused human-in-the-loop run state. Durable HITL would need the paused ``RunState`` stashed
20
+ separately; ``agent.py`` keeps pending runs in-process only (see ``PendingRuns``).
21
+
22
+ The durable store uses a vendored REST client (``session_store_client.py``) so the template needs no
23
+ unpublished dependency; swap it for the published package when it lands.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Optional
29
+
30
+ from agents import SQLiteSession, TResponseInputItem
31
+ from agents.memory import SessionABC
32
+
33
+ from databricks_agentkit.runtime.session_store_client import Session as _StoreSession
34
+ from databricks_agentkit.runtime.session_store_client import SessionStoreClient
35
+
36
+ # Fetch items oldest-first so the transcript replays in write order.
37
+ _ORDER_BY = "create_time asc"
38
+
39
+ # One in-process Session per session id, built lazily and shared — that's what makes multi-turn work
40
+ # in-process (the durable store needs no cache; each call resolves the same REST-backed session).
41
+ _local_sessions: dict[str, SQLiteSession] = {}
42
+
43
+
44
+ def session_store(
45
+ session_id: str, actor: str | None = None, store: str | None = None
46
+ ) -> SessionABC:
47
+ """The Session the agent persists conversation state to for ``session_id``.
48
+
49
+ In-memory ``SQLiteSession`` by default; a durable ``DatabricksSessionStore`` when a managed store
50
+ is configured. The store name resolves ``store`` arg → ``AGENT_SESSION_STORE`` env → the
51
+ ``[session_store]`` binding in agent.toml (`ab sessions bind`) → none. ``actor`` partitions the
52
+ durable store — the caller supplies it (typically the signed-in user), so each user's transcripts
53
+ stay separate; it defaults to ``session_id`` and is ignored by the in-memory store.
54
+ """
55
+ from databricks_agentkit.runtime.tool_manifest import resolve_session_store
56
+
57
+ store = resolve_session_store(store)
58
+ if store:
59
+ return DatabricksSessionStore(session_id, store, actor_id=actor or session_id)
60
+ cached = _local_sessions.get(session_id)
61
+ if cached is None:
62
+ cached = SQLiteSession(session_id) # ":memory:" — process-local, non-durable
63
+ _local_sessions[session_id] = cached
64
+ return cached
65
+
66
+
67
+ class DatabricksSessionStore(SessionABC):
68
+ """An Agents SDK ``Session`` over the Databricks Session Store REST API.
69
+
70
+ Each Responses input item is stored as one Session Store item whose ``data`` is the item dict
71
+ (opaque JSON to the store). ``get_items`` replays them in write order; ``pop_item`` and
72
+ ``clear_session`` are supported for the SDK's retry rollback path. The session is created on first
73
+ use (get-or-create), keyed by the conversation's ``session_id`` under the configured actor.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ session_id: str,
79
+ session_store_name: str,
80
+ *,
81
+ actor_id: str,
82
+ client: Optional[SessionStoreClient] = None,
83
+ workspace_client: Optional[Any] = None,
84
+ ) -> None:
85
+ if not session_store_name:
86
+ raise ValueError("session_store_name is required")
87
+ self.session_id = session_id
88
+ self._actor_id = actor_id
89
+ self._client = (client or SessionStoreClient(workspace_client)).set_session_store(
90
+ session_store_name
91
+ )
92
+ self._session: _StoreSession | None = None
93
+
94
+ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]:
95
+ items = await _run_sync(self._read_items)
96
+ return items[-limit:] if limit is not None else items
97
+
98
+ async def add_items(self, items: list[TResponseInputItem]) -> None:
99
+ if not items:
100
+ return
101
+ await _run_sync(lambda: self._client.append_items(self._resolve(), items=list(items)))
102
+
103
+ async def pop_item(self) -> TResponseInputItem | None:
104
+ # The REST store has no pop; re-list, clear, and re-append all but the last. Only used on the
105
+ # SDK's best-effort retry rollback, so the extra round-trip is acceptable and rare.
106
+ def _pop() -> TResponseInputItem | None:
107
+ items = self._read_items()
108
+ if not items:
109
+ return None
110
+ session = self._resolve()
111
+ self._client.clear_items(session)
112
+ if items[:-1]:
113
+ self._client.append_items(session, items=items[:-1])
114
+ return items[-1]
115
+
116
+ return await _run_sync(_pop)
117
+
118
+ async def clear_session(self) -> None:
119
+ await _run_sync(lambda: self._client.clear_items(self._resolve()))
120
+
121
+ # ----- internals ----------------------------------------------------------
122
+
123
+ def _resolve(self) -> _StoreSession:
124
+ if self._session is not None:
125
+ return self._session
126
+ try:
127
+ self._session = self._client.get_session(session_id=self.session_id)
128
+ except tuple(_not_found_errors()) as _: # type: ignore[misc]
129
+ self._session = self._client.create_session(
130
+ actor_id=self._actor_id,
131
+ session_id=self.session_id,
132
+ metadata={"client": "agentbricks-openai-agent"},
133
+ )
134
+ return self._session
135
+
136
+ def _read_items(self) -> list[TResponseInputItem]:
137
+ return [item.data for item in self._client.list_items(self._resolve(), order_by=_ORDER_BY)]
138
+
139
+
140
+ def _not_found_errors() -> tuple[type, ...]:
141
+ """Exception types that mean 'session does not exist yet'."""
142
+ try:
143
+ from databricks.sdk.errors import NotFound
144
+
145
+ return (NotFound,)
146
+ except ImportError: # pragma: no cover - SDK always present in practice
147
+ return (_SessionNotFound,)
148
+
149
+
150
+ class _SessionNotFound(Exception):
151
+ """Fallback 'not found' used only when databricks.sdk is unavailable."""
152
+
153
+
154
+ async def _run_sync(fn):
155
+ import asyncio
156
+
157
+ return await asyncio.get_running_loop().run_in_executor(None, fn)
File without changes
@@ -0,0 +1,64 @@
1
+ # Managed runtime internals
2
+
3
+ The managed runtime owns the HTTP lifecycle for one agent invocation: accepting an idempotent request,
4
+ scheduling work, storing status and events, returning results, and replaying events to a reconnecting
5
+ client. The public HTTP resource is an **invocation**; its UUID is also the idempotency key.
6
+
7
+ ```text
8
+ Client
9
+ │ POST /api/invocations, GET status/events
10
+ ▼
11
+ DurableAgentServer
12
+ HTTP adapter and @app.invoke / @app.recover hooks
13
+ ▼
14
+ Runtime
15
+ submit, wait/poll, and event replay
16
+ ├── RuntimeStore
17
+ │ accept/get/claim/complete/fail/events
18
+ │ ├── InMemoryRuntimeStore
19
+ │ └── LakebaseDurableRuntimeStore
20
+ └── InvocationExecutor
21
+ ├── LocalInvocationExecutor
22
+ └── DurableInvocationExecutor
23
+ ├── Heartbeat
24
+ └── RecoveryScheduler
25
+ ```
26
+
27
+ ## Lifecycle
28
+
29
+ `DurableAgentServer` uses `Runtime.from_environment(...)` for managed processes and
30
+ `Runtime.from_store(...)` when a caller supplies a store explicitly. The selected Runtime Store
31
+ determines the execution mode:
32
+
33
+ - `InMemoryRuntimeStore` uses `Runtime.local()` and `LocalInvocationExecutor`. It supports the same
34
+ foreground, background, polling, streaming, and replay APIs, but state ends with the process.
35
+ - `LakebaseDurableRuntimeStore` implements `DurableRuntimeStore`, so `Runtime.durable()` uses
36
+ `DurableInvocationExecutor`. It heartbeats the active attempt and can schedule recovery after a
37
+ worker stops heartbeating.
38
+
39
+ `RuntimeStore` deliberately has no heartbeat operation or heartbeat state. Those lease details are
40
+ owned by `DurableRuntimeStore` and its Lakebase implementation.
41
+
42
+ `RuntimeStore.accept(invocation_id, request)` defines idempotency. The same ID and request returns
43
+ the existing invocation; the same ID and different request raises `InvocationConflictError`.
44
+
45
+ The durable executor always scans for persisted `QUEUED` invocations, including after process
46
+ restart, and starts their first attempt through `@app.invoke`. Reads also schedule queued work as a
47
+ safety net when a request moves between replicas. `@app.recover` is optional: registering it enables
48
+ a separate scanner to replace stale `ACTIVE` attempts. Recovery is at least once, so agent side
49
+ effects must be idempotent.
50
+
51
+ ## Code map
52
+
53
+ | Path | Responsibility |
54
+ | --- | --- |
55
+ | `app.py` | FastAPI adapter and agent-hook registration |
56
+ | `runtime.py` | Runtime factories plus submit, polling, and replay facade |
57
+ | `store.py` | Shared Runtime Store contract and in-memory implementation |
58
+ | `execution.py` | Common attempt execution and process-local scheduler |
59
+ | `types.py` | Invocation records, contexts, errors, and JSON types |
60
+ | `durability/store.py` | Durable Store extension for leases and recovery claims |
61
+ | `durability/execution.py` | Durable scheduling and heartbeat-wrapped attempts |
62
+ | `durability/heartbeat.py` | Lease refresh lifecycle |
63
+ | `durability/recovery.py` | Stale-attempt scanning and recovery scheduling |
64
+ | `durability/lakebase_runtime_store.py` | Lakebase Runtime Store implementation |