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,107 @@
1
+ # Managed runtime integration contract for OpenAI Agents
2
+
3
+ This document owns the requirements shared by generated projects and existing agents adopting
4
+ the managed runtime. The [README](README.md) owns commands and HTTP examples; adapter docstrings own API details.
5
+ The adjacent template is an example, not a required agent architecture. Runtime internals live in
6
+ the installed package's runtime guide.
7
+
8
+ ## Command requirements
9
+
10
+ Agent Bricks CLI commands operate on an application directory; they do not rewrite its agent. Each capability
11
+ needs runtime wiring as well as manifest configuration.
12
+
13
+ | Command | Required integration |
14
+ | --- | --- |
15
+ | `dev`, `deploy` | Installable dependencies and an `app.yaml` command starting the application |
16
+ | `tools add mcp`, `uc-function`, `genie`, `sandbox` | Load bindings into agent tool lists and tool execution |
17
+ | `sessions bind` | Select the bound session store and supply session/actor identity |
18
+ | `memory bind` | Include bound, actor-scoped memory tools in the agent tool list |
19
+ | `tracing bind`, `tracing unbind` | Initialize tracing and open a root span from resolved config |
20
+ | `endpoint invoke` | An HTTP endpoint; the caller supplies its path and complete request body |
21
+
22
+ ### Project and startup
23
+
24
+ Use `agent.toml` for framework, tool/store bindings, and tracing. The existing `.agentbricks/project.toml`
25
+ file records framework and template provenance; keep it when working with projects that have it.
26
+ Metadata alone does not integrate adapters. Install a compatible `databricks-agentbricks[openai]`
27
+ distribution, supply the real command in `app.yaml`, load configuration before adapters, and listen
28
+ on the app port. The Agent Bricks CLI and runtime find `agent.toml` from the working directory or
29
+ `AGENTBRICKS_PROJECT_ROOT`. Keep credentials out of `app.yaml`.
30
+
31
+ Store binding commands declare intent. `dev` and `deploy` resolve or provision declared stores and
32
+ supply runtime config; deploy grants app access. The resolved Memory Store ID reaches the runtime
33
+ as `AGENT_MEMORY_STORE`, rather than being stored as an ID in the manifest. Do not hardcode values
34
+ that make later bindings ineffective.
35
+
36
+ The model provider is a choice, not a requirement. The template's [agent/agent.py](agent/agent.py)
37
+ wires the Databricks Unity Catalog AI Gateway (`AsyncDatabricksOpenAI(..., use_ai_gateway=True)`
38
+ plus a `system.ai.*` model), but an agent may keep its existing provider instead, e.g. direct
39
+ OpenAI via `OPENAI_API_KEY` and a model string like `gpt-4o-mini`. Whichever is chosen,
40
+ `configure()` sets it up at startup, and deployment must ensure the chosen model is reachable
41
+ from the app.
42
+
43
+ ### Tools
44
+
45
+ Use `databricks_agentkit.openai.mcp_servers()` for MCP, UC function, and sandbox bindings, combining
46
+ them with agent-owned servers without duplicates. It applies sandbox downscoping. Genie Agent
47
+ bindings arrive as tools from `genie_tools()`. Local Python tools belong in source; the template
48
+ discovers `@function_tool` functions through [agent/tools/__init__.py](agent/tools/__init__.py).
49
+
50
+ Feed selected tools into the agent's tool list while preserving approval policy. Tools that gate on
51
+ approval declare `needs_approval=True` and appear in the template's `REQUIRE_APPROVAL` set so
52
+ pending calls surface as interrupts. New bindings must become effective on the next agent
53
+ construction or restart without another source edit.
54
+
55
+ ### Sessions and memory
56
+
57
+ Pass `session_store(session_id)` as the `session` argument of `Runner.run(agent, messages,
58
+ session=...)` for managed conversation state. The helper selects the bound Session Store or
59
+ an in-process session; explicit arguments and environment overrides take precedence. Supply the
60
+ actor so the durable store partitions transcripts per user. It caches the in-process session per
61
+ process, so restart after a binding change.
62
+
63
+ Include `memory_tools(actor)` in the agent's tool list. It returns no tools when unconfigured.
64
+ Its closures capture actor identity, so never reuse them across users. The application owns
65
+ trusted actor/tenant authentication; a payload field alone does not establish identity.
66
+
67
+ ### Tracing
68
+
69
+ Call `configure_tracing()` after loading config; it binds `mlflow.openai.autolog`. Then wrap agent
70
+ execution, including streams, in `start_trace(..., session_id=session_id)`. The root span lets
71
+ framework autologging record children. The helper enables tracing when destination and experiment
72
+ settings are present and disables it otherwise. Preserve existing tracing semantics when composing
73
+ it.
74
+
75
+ ## Managed server adapter
76
+
77
+ For the managed invocation protocol, construct `DurableAgentServer` in [runtime/main.py](runtime/main.py)
78
+ and register [runtime/adapter.py](runtime/adapter.py) hooks. Keep framework-native execution in
79
+ [agent/agent.py](agent/agent.py), independent of runtime request/context types.
80
+
81
+ The invoke hook translates opaque application input, runs the agent through the framework `Runner`,
82
+ translates native events, calls `await context.emit(event)`, and returns JSON output. DurableAgentServer owns
83
+ foreground/background transport, polling, and replay. Invocation UUIDs differ from stable
84
+ application session IDs; the routing cookie is not the application session.
85
+
86
+ The example assumes `messages` input and message, delta, and interrupt events. Custom inputs,
87
+ outputs, and interruptions require explicit mappings and must not be discarded to fit the example.
88
+
89
+ ### Recovery and durability
90
+
91
+ Runtime Store persistence covers invocations and emitted events. Session Store persistence covers
92
+ the conversation transcript, not in-flight run state. The Agents SDK has no node-level checkpoint
93
+ continuation: OpenAI HITL `RunState` is process-local and does not survive worker loss even with a
94
+ Session Store bound. `dev` uses process-local invocation storage; deployment attaches a
95
+ Lakebase-backed Runtime Store.
96
+
97
+ Register recovery when intended. The recover hook replays the persisted application input against
98
+ the same session and prepends a developer instruction warning that the prior attempt may have
99
+ partially completed. Recovery is at least once, so side effects must be idempotent and tools must
100
+ tolerate replay.
101
+
102
+ ## Optional chat app
103
+
104
+ No CLI command requires the chat UI. The default overlay supplies it; `--disable-chat-app` omits
105
+ it. When adopting it, inspect `CHAT_APP.md`, `runtime/ui.py`, and browser code. Adapt model choice,
106
+ history, approval interrupts, and authentication to the actual agent. Its message-oriented
107
+ assumptions must not silently change custom application behavior.
@@ -0,0 +1,76 @@
1
+ # Agent Development Guide
2
+
3
+ This project is an OpenAI Agents SDK workload hosted by `databricks_agentkit.DurableAgentServer`.
4
+
5
+ Read [AGENTKIT_CONTRACT.md](AGENTKIT_CONTRACT.md) before changing integration points. It owns command
6
+ requirements, tool/state/tracing wiring, and recovery. [README.md](README.md) owns setup and client
7
+ examples. Keep this file as a development map rather than repeating those rules.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ ab dev
13
+ uv run pytest
14
+ ab --profile <profile> deploy <name> --source .
15
+ ```
16
+
17
+ ## Request contract
18
+
19
+ Use only `/api/invocations`. The transport body is:
20
+
21
+ ```json
22
+ {
23
+ "id": "<uuid>",
24
+ "input": {
25
+ "session_id": "<stable-application-session>",
26
+ "messages": [{"role": "user", "content": "hello"}],
27
+ "resume": null,
28
+ "model": "optional-serving-endpoint"
29
+ },
30
+ "background": false,
31
+ "stream": false
32
+ }
33
+ ```
34
+
35
+ `id` is the invocation identifier and idempotency key. `background` and `stream` are transport
36
+ fields. Everything framework-specific belongs inside `input`. The browser generates a stable
37
+ session ID in local storage; API clients should do the same. The Apps router cookie is only for
38
+ sticky routing and is not authentication or application session state.
39
+
40
+ ## Code map
41
+
42
+ | Change | File |
43
+ | --- | --- |
44
+ | Framework-native agent and `run_agent` | `agent/agent.py` |
45
+ | Local tools | `agent/tools/` |
46
+ | MCP servers | `agent/mcps.py` |
47
+ | Managed runtime `invoke`/`recover` hooks and input/output translation | `runtime/adapter.py` |
48
+ | `DurableAgentServer` construction and hook registration | `runtime/main.py` |
49
+ | Browser and managed-state routes | `runtime/ui.py` |
50
+ | Browser behavior | `ui/app.js` |
51
+
52
+ Keep `agent/agent.py` runnable without runtime request or context types. If you bring an existing agent,
53
+ put its framework-native execution in `run_agent`. The small `runtime/adapter.py` is the agent-author
54
+ integration point: it translates the application payload, calls `run_agent`, emits runtime events, and
55
+ shapes the response. Its `recover` hook calls the same `run_agent` with the original application input
56
+ plus a developer instruction warning that the prior attempt may have partially completed because the
57
+ Agents SDK does not expose checkpoint continuation.
58
+
59
+ ## State and recovery
60
+
61
+ - Invocation state/events: in-memory in `ab dev`; Lakebase when `ab deploy` attaches a Runtime
62
+ Store.
63
+ - Conversation transcript: in-process in `ab dev`; managed Session Store when bound, on `ab deploy`.
64
+ - Long-term memory: off in `ab dev`; managed Memory Store when bound, on `ab deploy`.
65
+ - OpenAI HITL `RunState`: process-local even with Session Store; it does not survive worker loss.
66
+ - Recovery: replay the persisted application input against the same session.
67
+
68
+ The adapter sends every translated framework event through `context.emit()` before delivery. OpenAI
69
+ Agents SDK does not expose node-level checkpoint continuation, so side effects remain at-least-once
70
+ and tools must be idempotent.
71
+
72
+ ## Tools
73
+
74
+ `agent/tools/all_tools()` auto-imports tool modules. Add a decorated tool file rather than manually
75
+ editing a registry. Approval tools must declare `needs_approval=True` and appear in
76
+ `REQUIRE_APPROVAL`.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,143 @@
1
+ # OpenAI Agents template
2
+
3
+ An OpenAI Agents SDK agent served by `databricks_agentkit.DurableAgentServer`. The managed runtime keeps invocation state and
4
+ events in memory during `ab dev`. Deployment attaches a persistent Runtime Store, so invocation
5
+ state and events survive process loss and interrupted work can be recovered.
6
+
7
+ The generated project separates portable agent execution from the managed HTTP protocol:
8
+
9
+ ```text
10
+ client -> runtime/main.py -> runtime/adapter.py -> agent/agent.py:run_agent
11
+ ```
12
+
13
+ - `agent/agent.py` owns the framework-native agent, sessions, tools, MCP lifetime, HITL state, and
14
+ `run_agent`.
15
+ - `runtime/adapter.py` owns the agent-author integration hooks: runtime input/output translation plus
16
+ `invoke` and `recover`.
17
+ - `runtime/main.py` constructs the server and registers those hooks.
18
+
19
+ To bring an existing Agents SDK agent, keep its normal execution code in `agent/agent.py`, expose a
20
+ `run_agent` function that returns the native streaming result, and make only the small payload/event
21
+ mapping changes needed in `runtime/adapter.py`.
22
+
23
+ ## Run locally
24
+
25
+ ```bash
26
+ ab dev
27
+ ```
28
+
29
+ The API is available at `http://localhost:8000/api/invocations`. Every request supplies a UUID `id`.
30
+ That ID is the invocation identifier and idempotency key. Agent-specific values live inside the
31
+ opaque `input` object:
32
+
33
+ ```bash
34
+ SESSION_ID=$(uuidgen)
35
+ INVOCATION_ID=$(uuidgen)
36
+
37
+ curl -sS http://localhost:8000/api/invocations \
38
+ -H 'Content-Type: application/json' \
39
+ -d "{\"id\":\"$INVOCATION_ID\",\"input\":{\"session_id\":\"$SESSION_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"What time is it? Use your tool.\"}]}}"
40
+ ```
41
+
42
+ Reuse `SESSION_ID` for multi-turn conversation history. Generate a new `INVOCATION_ID` for each
43
+ turn. Retrying the same request with the same invocation ID returns the persisted result; changing
44
+ the request while reusing the ID returns `409`.
45
+
46
+ ## Invocation modes
47
+
48
+ - Foreground: omit `background` and `stream`; the response contains the agent result under `output`.
49
+ - Foreground streaming: set `stream: true`; the response is SSE backed by persisted events.
50
+ - Background: set `background: true`; poll the returned `status_url`.
51
+ - Background streaming: set both flags; the `202` response includes `status_url` and `events_url`.
52
+
53
+ ```bash
54
+ INVOCATION_ID=$(uuidgen)
55
+ curl -sN http://localhost:8000/api/invocations \
56
+ -H 'Content-Type: application/json' \
57
+ -d "{\"id\":\"$INVOCATION_ID\",\"input\":{\"session_id\":\"$SESSION_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"Count to three.\"}]},\"stream\":true}"
58
+
59
+ INVOCATION_ID=$(uuidgen)
60
+ curl -sS http://localhost:8000/api/invocations \
61
+ -H 'Content-Type: application/json' \
62
+ -d "{\"id\":\"$INVOCATION_ID\",\"input\":{\"session_id\":\"$SESSION_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"Summarize durable agents.\"}]},\"background\":true}" | jq
63
+ curl -sS "http://localhost:8000/api/invocations/$INVOCATION_ID" | jq
64
+ ```
65
+
66
+ SSE records contain events translated by `runtime/adapter.py`: token `delta`s, completed `message`s,
67
+ and HITL `interrupt`s. Replay from a cursor with
68
+ `GET /api/invocations/{id}/events?after={sequence}`.
69
+
70
+ ## Human approval
71
+
72
+ `send_message` requires approval. When output or the event stream contains an `interrupt`, submit a
73
+ new invocation with the same application session:
74
+
75
+ ```json
76
+ {
77
+ "id": "<new-uuid>",
78
+ "input": {
79
+ "session_id": "<same-session-id>",
80
+ "resume": {"decisions": [{"type": "approve"}]}
81
+ }
82
+ }
83
+ ```
84
+
85
+ The paused Agents SDK `RunState` is process-local. A managed Session Store preserves transcript
86
+ history, but not a pending approval across restarts or replicas.
87
+
88
+ ## Crash recovery
89
+
90
+ `runtime/main.py` always registers the adapter's `invoke` and `recover` hooks. Both call the same
91
+ `agent.agent.run_agent` function. OpenAI Agents SDK does not currently expose LangGraph-style node
92
+ checkpoints, so `recover` replays the original application input against the same session. The
93
+ adapter prepends a developer instruction telling the agent that this is a recovery attempt and that
94
+ some tool calls or external side effects may already have completed or may still be in progress.
95
+ When deployment attaches a Runtime Store, invocation state and emitted events survive process loss
96
+ and the runtime can call `recover` on a replacement worker. Without a Runtime Store, invocation state
97
+ remains process-local and interrupted work is not automatically recovered. External side effects
98
+ remain at-least-once and must be idempotent.
99
+
100
+ ## Chat app
101
+
102
+ The browser UI is included by default. It generates a stable application session ID in local
103
+ storage, places it inside each invocation's `input`, and generates a fresh invocation UUID per turn.
104
+ Use `ab init --framework openai --disable-chat-app` for API-only output.
105
+
106
+ ## Configure and deploy
107
+
108
+ - Change the model, instructions, tools, and framework-native execution in `agent/agent.py`.
109
+ - Change `runtime/adapter.py` only to map a different application input/output contract.
110
+ - Add local tools under `agent/tools/`; modules are auto-discovered.
111
+ - Add MCP servers in `agent/mcps.py` or with `ab tools add mcp`.
112
+ - Bind long-term memory with `ab memory bind <store>`.
113
+ - Bind durable transcript history with `ab sessions bind <store>`.
114
+
115
+ ```bash
116
+ ab --profile <profile> deploy agent-openai --source .
117
+ ```
118
+
119
+ When deployment provisions a dedicated Runtime Store, only the app-owned
120
+ `databricks_agentkit_runtime_<hash>` schema and runtime tables are added. Managed Runtime Store
121
+ deployments use their own default schema.
122
+
123
+ The `__Host-databricks-app-router` cookie may be supplied independently for sticky replica routing.
124
+ It is not authentication and is not used as the template's application session ID.
125
+
126
+ # Request-user authorization
127
+
128
+ Declared tools in `agent.toml` select `auth = "user"` or `auth = "app"`; legacy missing auth
129
+ continues to use the application/default identity. Deployed user tools require the request resolver
130
+ and never fall back to application credentials. Model, memory-service, session-service, and custom
131
+ MCP server credentials are unchanged.
132
+
133
+ `runtime/main.py` derives the invocation policy after `configure()`. The runtime adapter namespaces
134
+ public session IDs and actor values for the request owner, then passes only `workspace_client_for`
135
+ to the framework-native agent. Internal session keys are never returned to clients.
136
+
137
+ User-policy invocations use the existing Runtime for synchronous, streaming, and background calls,
138
+ including status polling, event replay, and invocation-ID idempotency. The Runtime Store persists no
139
+ credential; request-user authentication remains process-local for the active first attempt. A
140
+ replacement attempt fails with `MCP_USER_AUTH_RECOVERY_UNSUPPORTED` before agent code runs. Approval
141
+ interruptions remain unsupported and fail with `MCP_USER_AUTH_HITL_UNSUPPORTED`, without storing a
142
+ credential-bearing `RunState`. Existing namespaced memory and conversation-store behavior is
143
+ unchanged; OBO does not add another saver.
@@ -0,0 +1,205 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from collections.abc import AsyncIterator, Callable
5
+ from contextlib import asynccontextmanager, suppress
6
+ from typing import Any
7
+
8
+ from agents import Agent, Runner, RunResultStreaming, RunState
9
+ from agents.mcp import MCPServerManager
10
+ from databricks.sdk import WorkspaceClient
11
+ from databricks_openai import AsyncDatabricksOpenAI
12
+
13
+ from agent.mcps import build_mcp_servers
14
+
15
+ # Importing the tools package auto-registers every tool module.
16
+ from agent.tools import all_tools
17
+ from databricks_agentkit import workspace_client, workspace_headers
18
+ from databricks_agentkit.openai import (
19
+ configure_tracing,
20
+ genie_tools,
21
+ mcp_servers,
22
+ memory_tools,
23
+ session_store,
24
+ start_trace,
25
+ )
26
+ from databricks_agentkit.runtime.auth import AuthError
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # A Unity Catalog AI Gateway model service, served from the `system.ai` schema and queried through
31
+ # the gateway (see `use_ai_gateway=True` in configure()). Swap for any `system.ai.*` model service
32
+ # your workspace exposes — the demo chat app's picker lists what's available.
33
+ MODEL = "system.ai.claude-sonnet-4-5"
34
+
35
+ # Tools that require human approval before they run. Add a tool's name here and the agent pauses when
36
+ # the model calls it, emitting an `interrupt` event; the client resumes by sending `resume` with the
37
+ # same session id. The tools declare `needs_approval=True` themselves (see agent/tools/); this set is
38
+ # how the runtime knows which pending calls to surface. Empty it to disable approval gating.
39
+ REQUIRE_APPROVAL = {"send_message"}
40
+
41
+ # OpenAI Sessions persist transcript history, not a paused RunState. Keep pending approvals local.
42
+ _pending_runs: dict[str, RunState] = {}
43
+
44
+
45
+ def configure() -> None:
46
+ """Wire up global state; call once at server startup (not at import)."""
47
+ _check_databricks_auth()
48
+ from agents import set_default_openai_api, set_default_openai_client
49
+
50
+ # use_ai_gateway routes to the Unity Catalog AI Gateway (`<host>/ai-gateway/mlflow/v1`), so
51
+ # `MODEL` is a `system.ai.*` model name rather than a serving-endpoint name.
52
+ set_default_openai_client(
53
+ AsyncDatabricksOpenAI(
54
+ workspace_client=workspace_client(),
55
+ default_headers=workspace_headers() or None,
56
+ use_ai_gateway=True,
57
+ )
58
+ )
59
+ set_default_openai_api("chat_completions")
60
+ configure_tracing()
61
+
62
+
63
+ def _check_databricks_auth() -> None:
64
+ """Fail fast at startup with a clear message if Databricks auth isn't configured.
65
+
66
+ Without this, a missing/invalid profile only surfaces on the first model call — as a generic SDK
67
+ error buried in a request traceback. Resolving a WorkspaceClient here validates the same config
68
+ the model client uses, so the failure is immediate and actionable.
69
+ """
70
+ try:
71
+ workspace_client()
72
+ except Exception as e:
73
+ profile = os.getenv("DATABRICKS_CONFIG_PROFILE")
74
+ target = (
75
+ f"profile {profile!r}" if profile else "the DEFAULT profile / DATABRICKS_HOST+TOKEN"
76
+ )
77
+ raise RuntimeError(
78
+ f"Databricks auth is not configured — the agent can't call the model. Tried {target}.\n"
79
+ "Fix one of:\n"
80
+ " • set DATABRICKS_CONFIG_PROFILE in .env to a profile from `databricks auth profiles`, or\n"
81
+ " • run `databricks auth login --profile <name>` to create one, or\n"
82
+ " • set DATABRICKS_HOST and DATABRICKS_TOKEN in .env.\n"
83
+ f"(underlying error: {e})"
84
+ ) from e
85
+
86
+
87
+ def create_agent(
88
+ actor: str,
89
+ mcp=None,
90
+ model: str | None = None,
91
+ *,
92
+ workspace_client_for: Callable[[str], WorkspaceClient] | None = None,
93
+ ) -> Agent:
94
+ """Build the OpenAI Agents SDK agent: tools, memory, MCP servers, and model."""
95
+ return Agent(
96
+ name="Agent",
97
+ instructions="You are a helpful assistant.",
98
+ model=model or MODEL,
99
+ tools=[
100
+ *all_tools(),
101
+ *memory_tools(actor),
102
+ *genie_tools(workspace_client_for=workspace_client_for),
103
+ ],
104
+ mcp_servers=mcp or [],
105
+ )
106
+
107
+
108
+ def resume_agent(session_id: str, resume: dict[str, Any]) -> RunState:
109
+ """Apply human decisions to a paused run and return the native RunState."""
110
+ state = _pending_runs.pop(session_id, None)
111
+ if state is None:
112
+ raise RuntimeError(
113
+ "No paused run for this session. HITL pauses are in-process only, so a restart or a "
114
+ "different replica loses them; retry the turn."
115
+ )
116
+ decisions = resume.get("decisions") or []
117
+ for decision, item in zip(decisions, state.get_interruptions(), strict=False):
118
+ if decision.get("type") == "approve":
119
+ state.approve(item)
120
+ else:
121
+ state.reject(item, rejection_message=decision.get("message"))
122
+ return state
123
+
124
+
125
+ @asynccontextmanager
126
+ async def run_agent(
127
+ agent_input: list[Any] | RunState,
128
+ *,
129
+ session_id: str,
130
+ actor: str | None = None,
131
+ model: str | None = None,
132
+ workspace_client_for: Callable[[str], WorkspaceClient] | None = None,
133
+ ) -> AsyncIterator[RunResultStreaming]:
134
+ """Run the agent and expose its native streaming result.
135
+
136
+ This is the framework-native entrypoint. It has no dependency on Agent Bricks request or context types,
137
+ so it can be called from another server, a notebook, or a test harness.
138
+ """
139
+ actor = actor or session_id
140
+ auth_kwargs = {"workspace_client_for": workspace_client_for} if workspace_client_for else {}
141
+ servers = await mcp_servers(build_mcp_servers(), **auth_kwargs)
142
+ async with MCPServerManager(servers) as manager:
143
+ for server, error in manager.errors.items():
144
+ if getattr(server, "_agentbricks_request_user", False) is True or isinstance(
145
+ error, AuthError
146
+ ):
147
+ raise error
148
+ active_servers = []
149
+ for server in manager.active_servers:
150
+ tool_filter = server.tool_filter
151
+ try:
152
+ server.tool_filter = None
153
+ server.cache_tools_list = True
154
+ async with asyncio.timeout(manager.connect_timeout_seconds):
155
+ await server.list_tools()
156
+ except Exception as error:
157
+ if getattr(server, "_agentbricks_request_user", False) is True or isinstance(
158
+ error, AuthError
159
+ ):
160
+ raise
161
+ logger.warning(
162
+ "Failed to list tools from MCP server %r; continuing without it.",
163
+ server.name,
164
+ exc_info=True,
165
+ )
166
+ else:
167
+ active_servers.append(server)
168
+ finally:
169
+ server.tool_filter = tool_filter
170
+
171
+ agent = create_agent(
172
+ actor,
173
+ active_servers,
174
+ model=model,
175
+ workspace_client_for=workspace_client_for,
176
+ )
177
+ with start_trace(name="invoke", inputs=agent_input, session_id=session_id) as span:
178
+ if isinstance(agent_input, RunState):
179
+ result = Runner.run_streamed(agent, agent_input)
180
+ else:
181
+ result = Runner.run_streamed(
182
+ agent,
183
+ agent_input,
184
+ session=session_store(session_id, actor),
185
+ )
186
+
187
+ try:
188
+ yield result
189
+ finally:
190
+ if workspace_client_for is not None and not result.is_complete:
191
+ result.cancel()
192
+ with suppress(asyncio.CancelledError, Exception):
193
+ async for _ in result.stream_events():
194
+ pass
195
+
196
+ if result.interruptions:
197
+ if workspace_client_for is not None:
198
+ raise AuthError(
199
+ "MCP_USER_AUTH_HITL_UNSUPPORTED",
200
+ "Request-user invocations do not support paused approvals.",
201
+ 400,
202
+ )
203
+ _pending_runs[session_id] = result.to_state()
204
+ if span is not None:
205
+ span.set_outputs({"output": result.final_output})
@@ -0,0 +1,28 @@
1
+ """MCP servers to offer the agent — this is where you configure them.
2
+
3
+ Empty by default: the agent runs with only the MCP servers declared in ``agent.toml``. Add servers
4
+ to ``build_mcp_servers`` to offer more; ``agent.py`` joins them with the ``agent.toml`` servers and
5
+ omits any that fail to connect or list tools.
6
+ """
7
+
8
+ from databricks_openai.agents import McpServer
9
+
10
+
11
+ def build_mcp_servers() -> list[McpServer]:
12
+ """Return the MCP servers to offer the agent. Empty by default — add your own.
13
+
14
+ Example (a Databricks-managed MCP, authed as the app service principal)::
15
+
16
+ from databricks.sdk import WorkspaceClient
17
+
18
+ client = WorkspaceClient()
19
+ host = client.config.host.rstrip("/")
20
+ return [
21
+ McpServer(
22
+ url=f"{host}/api/2.0/mcp/functions/system/ai",
23
+ name="system-ai",
24
+ workspace_client=client,
25
+ ),
26
+ ]
27
+ """
28
+ return []
@@ -0,0 +1,24 @@
1
+ """Agent tools package.
2
+
3
+ Every module here is auto-imported, and every OpenAI Agents SDK ``FunctionTool`` it defines (via the
4
+ ``@function_tool`` decorator) is collected by ``all_tools()``. Drop a new ``*.py`` into this folder
5
+ with a ``@function_tool``-decorated function and it's picked up automatically — no wiring to edit.
6
+ ``create_agent`` uses ``all_tools()``.
7
+ """
8
+
9
+ import importlib
10
+ import inspect
11
+ import pkgutil
12
+
13
+ from agents import FunctionTool
14
+
15
+
16
+ def all_tools() -> list[FunctionTool]:
17
+ """Every FunctionTool defined across the modules in this package."""
18
+ tools: list[FunctionTool] = []
19
+ for module in pkgutil.iter_modules(__path__):
20
+ mod = importlib.import_module(f"{__name__}.{module.name}")
21
+ for _, obj in inspect.getmembers(mod, lambda o: isinstance(o, FunctionTool)):
22
+ if obj not in tools: # a tool imported into several modules is collected once
23
+ tools.append(obj)
24
+ return tools
@@ -0,0 +1,15 @@
1
+ """Sample tool. A working example — add your own tools as new files in this package.
2
+
3
+ Decorate a function with ``@function_tool`` (OpenAI Agents SDK) and it becomes an agent tool; the
4
+ package auto-collects it via ``all_tools()``, which ``create_agent`` uses.
5
+ """
6
+
7
+ from datetime import datetime
8
+
9
+ from agents import function_tool
10
+
11
+
12
+ @function_tool
13
+ def get_current_time() -> str:
14
+ """Get the current date and time."""
15
+ return datetime.now().isoformat()
@@ -0,0 +1,18 @@
1
+ """A side-effecting sample tool, gated by human approval.
2
+
3
+ Unlike ``get_current_time`` (a harmless read), this tool stands in for an action with real
4
+ consequences — sending a message. ``needs_approval=True`` makes the Agents SDK pause before it runs
5
+ and surface a pending approval; the runtime relays that as an ``interrupt`` event and resumes on an
6
+ approve/reject decision (its name is also listed in ``REQUIRE_APPROVAL`` in ``agent/agent.py``). Swap
7
+ the body for a real send; the approval gate is what the template is demonstrating.
8
+ """
9
+
10
+ from agents import function_tool
11
+
12
+
13
+ @function_tool(needs_approval=True)
14
+ def send_message(recipient: str, body: str) -> str:
15
+ """Send a message to a recipient. Use when the user asks to notify or message someone."""
16
+ # A real implementation would call an email/Slack/SMS API here. The point of this template is the
17
+ # human-approval gate in front of it (needs_approval=True), not the delivery.
18
+ return f"Message sent to {recipient}: {body}"
@@ -0,0 +1,15 @@
1
+ command: ["uv", "run", "start-server"]
2
+ # databricks apps listen by default on port 8000
3
+ #
4
+ # Agent Bricks injects the deployed Runtime Store endpoint. The Agents SDK session remains
5
+ # in-process unless a managed Session Store is bound. Tracing is bound with `ab tracing bind
6
+ # --experiment-name <path>` (ab deploy then wires the env below; `ab dev` traces to a local
7
+ # MLflow server). To set a deployment's tracing env by hand instead:
8
+ #
9
+ # env:
10
+ # # Tracing (set a destination + an experiment). Destination: MLFLOW_TRACKING_URI or
11
+ # # MLFLOW_TRACING_DESTINATION. Experiment: MLFLOW_EXPERIMENT_ID or MLFLOW_EXPERIMENT_NAME.
12
+ # - name: MLFLOW_TRACKING_URI
13
+ # value: "databricks"
14
+ # - name: MLFLOW_EXPERIMENT_ID
15
+ # value: "<experiment-id>"