@databricks/appkit 0.83.0 → 0.84.0

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 (210) hide show
  1. package/CLAUDE.md +15 -1
  2. package/dist/agents/databricks.d.ts +15 -3
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +25 -8
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +2 -2
  8. package/dist/cache/index.d.ts.map +1 -1
  9. package/dist/cache/index.js +6 -1
  10. package/dist/cache/index.js.map +1 -1
  11. package/dist/cli/commands/agent/eval.js +1 -1
  12. package/dist/cli/commands/generate-types.js +1 -1
  13. package/dist/cli/commands/plugin/sync/sync.js +28 -15
  14. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  15. package/dist/cli/commands/registry/add.js +3 -11
  16. package/dist/cli/commands/registry/add.js.map +1 -1
  17. package/dist/cli/commands/registry/config-writer.js +1 -1
  18. package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
  19. package/dist/connectors/lakebase/routing-pool.js +4 -10
  20. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  21. package/dist/context/caller-context.d.ts +4 -1
  22. package/dist/context/caller-context.d.ts.map +1 -1
  23. package/dist/context/caller-context.js.map +1 -1
  24. package/dist/context/execution-context.d.ts +29 -1
  25. package/dist/context/execution-context.d.ts.map +1 -1
  26. package/dist/context/execution-context.js +60 -8
  27. package/dist/context/execution-context.js.map +1 -1
  28. package/dist/context/index.d.ts +2 -2
  29. package/dist/context/index.js +2 -1
  30. package/dist/context/request-scope.d.ts +2 -0
  31. package/dist/context/request-scope.js +39 -0
  32. package/dist/context/request-scope.js.map +1 -0
  33. package/dist/context/resource-capabilities.js +59 -0
  34. package/dist/context/resource-capabilities.js.map +1 -0
  35. package/dist/context/scoped-api.js +104 -0
  36. package/dist/context/scoped-api.js.map +1 -0
  37. package/dist/context/service-context.d.ts.map +1 -1
  38. package/dist/context/service-context.js +1 -1
  39. package/dist/context/service-context.js.map +1 -1
  40. package/dist/context/user-context.d.ts +16 -19
  41. package/dist/context/user-context.d.ts.map +1 -1
  42. package/dist/context/user-context.js +30 -3
  43. package/dist/context/user-context.js.map +1 -1
  44. package/dist/core/agent/load-agents.d.ts.map +1 -1
  45. package/dist/core/agent/load-agents.js +5 -2
  46. package/dist/core/agent/load-agents.js.map +1 -1
  47. package/dist/core/agent/run-agent.d.ts +19 -6
  48. package/dist/core/agent/run-agent.d.ts.map +1 -1
  49. package/dist/core/agent/run-agent.js +53 -17
  50. package/dist/core/agent/run-agent.js.map +1 -1
  51. package/dist/core/agent/types.d.ts +18 -1
  52. package/dist/core/agent/types.d.ts.map +1 -1
  53. package/dist/core/agent/types.js.map +1 -1
  54. package/dist/core/appkit.d.ts +4 -3
  55. package/dist/core/appkit.d.ts.map +1 -1
  56. package/dist/core/appkit.js +30 -9
  57. package/dist/core/appkit.js.map +1 -1
  58. package/dist/core/plugin-context.d.ts +14 -15
  59. package/dist/core/plugin-context.d.ts.map +1 -1
  60. package/dist/core/plugin-context.js +24 -14
  61. package/dist/core/plugin-context.js.map +1 -1
  62. package/dist/errors/base.d.ts +2 -2
  63. package/dist/errors/base.js +2 -2
  64. package/dist/errors/base.js.map +1 -1
  65. package/dist/errors/identity-expired.d.ts +14 -0
  66. package/dist/errors/identity-expired.d.ts.map +1 -0
  67. package/dist/errors/identity-expired.js +33 -0
  68. package/dist/errors/identity-expired.js.map +1 -0
  69. package/dist/errors/index.js +1 -0
  70. package/dist/index.d.ts +8 -5
  71. package/dist/index.js +5 -2
  72. package/dist/logging/logger.js +1 -1
  73. package/dist/plugin/execution-result.d.ts +4 -1
  74. package/dist/plugin/execution-result.d.ts.map +1 -1
  75. package/dist/plugin/interceptors/telemetry.js +6 -4
  76. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  77. package/dist/plugin/plugin.d.ts +13 -21
  78. package/dist/plugin/plugin.d.ts.map +1 -1
  79. package/dist/plugin/plugin.js +41 -122
  80. package/dist/plugin/plugin.js.map +1 -1
  81. package/dist/plugins/agents/agents.d.ts +12 -0
  82. package/dist/plugins/agents/agents.d.ts.map +1 -1
  83. package/dist/plugins/agents/agents.js +65 -15
  84. package/dist/plugins/agents/agents.js.map +1 -1
  85. package/dist/plugins/agents/auth-mode.js +42 -0
  86. package/dist/plugins/agents/auth-mode.js.map +1 -0
  87. package/dist/plugins/agents/index.d.ts +1 -1
  88. package/dist/plugins/agents/mlflow.js +11 -3
  89. package/dist/plugins/agents/mlflow.js.map +1 -1
  90. package/dist/plugins/agents/tool-dispatch.js +9 -1
  91. package/dist/plugins/agents/tool-dispatch.js.map +1 -1
  92. package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
  93. package/dist/plugins/ai-search/ai-search.js +4 -4
  94. package/dist/plugins/ai-search/ai-search.js.map +1 -1
  95. package/dist/plugins/analytics/analytics.d.ts +2 -2
  96. package/dist/plugins/analytics/analytics.js +6 -6
  97. package/dist/plugins/analytics/analytics.js.map +1 -1
  98. package/dist/plugins/database/crud/contract.js +2 -2
  99. package/dist/plugins/database/crud/contract.js.map +1 -1
  100. package/dist/plugins/files/plugin.d.ts +10 -4
  101. package/dist/plugins/files/plugin.d.ts.map +1 -1
  102. package/dist/plugins/files/plugin.js +34 -13
  103. package/dist/plugins/files/plugin.js.map +1 -1
  104. package/dist/plugins/genie/genie.d.ts +9 -0
  105. package/dist/plugins/genie/genie.d.ts.map +1 -1
  106. package/dist/plugins/genie/genie.js +12 -3
  107. package/dist/plugins/genie/genie.js.map +1 -1
  108. package/dist/plugins/genie/manifest.js +1 -0
  109. package/dist/plugins/jobs/plugin.js +2 -2
  110. package/dist/plugins/jobs/plugin.js.map +1 -1
  111. package/dist/plugins/lakebase/lakebase.d.ts +10 -17
  112. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  113. package/dist/plugins/lakebase/lakebase.js +12 -17
  114. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  115. package/dist/plugins/server/client-config-sanitizer.js +1 -4
  116. package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
  117. package/dist/plugins/server/dev-obo-middleware.js +60 -0
  118. package/dist/plugins/server/dev-obo-middleware.js.map +1 -0
  119. package/dist/plugins/server/index.d.ts.map +1 -1
  120. package/dist/plugins/server/index.js +5 -2
  121. package/dist/plugins/server/index.js.map +1 -1
  122. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
  123. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  124. package/dist/plugins/server/static-server.js +3 -3
  125. package/dist/plugins/server/static-server.js.map +1 -1
  126. package/dist/plugins/server/utils.js +3 -3
  127. package/dist/plugins/server/utils.js.map +1 -1
  128. package/dist/plugins/server/vite-dev-server.js +4 -4
  129. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  130. package/dist/plugins/serving/manifest.js +1 -0
  131. package/dist/plugins/serving/serving.js +6 -6
  132. package/dist/plugins/serving/serving.js.map +1 -1
  133. package/dist/schemas/manifest.d.ts +35 -1
  134. package/dist/schemas/manifest.d.ts.map +1 -1
  135. package/dist/schemas/manifest.js +112 -4
  136. package/dist/schemas/manifest.js.map +1 -1
  137. package/dist/shared/src/dev-obo.js +86 -0
  138. package/dist/shared/src/dev-obo.js.map +1 -0
  139. package/dist/shared/src/index.d.ts +1 -1
  140. package/dist/shared/src/plugin.d.ts +12 -1
  141. package/dist/shared/src/plugin.d.ts.map +1 -1
  142. package/dist/shared/src/schemas/manifest.d.ts +41 -33
  143. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  144. package/dist/shared/src/schemas/manifest.js +43 -4
  145. package/dist/shared/src/schemas/manifest.js.map +1 -1
  146. package/dist/stream/stream-manager.d.ts.map +1 -1
  147. package/dist/stream/stream-manager.js +5 -2
  148. package/dist/stream/stream-manager.js.map +1 -1
  149. package/dist/telemetry/execution-span-processor.js +21 -0
  150. package/dist/telemetry/execution-span-processor.js.map +1 -0
  151. package/dist/telemetry/telemetry-manager.js +2 -1
  152. package/dist/telemetry/telemetry-manager.js.map +1 -1
  153. package/dist/testing/create-test-app.d.ts +2 -2
  154. package/dist/testing/create-test-app.js.map +1 -1
  155. package/dist/testing/test-plugin-context.d.ts +3 -12
  156. package/dist/testing/test-plugin-context.d.ts.map +1 -1
  157. package/dist/testing/test-plugin-context.js +24 -16
  158. package/dist/testing/test-plugin-context.js.map +1 -1
  159. package/dist/type-generator/database/generate.js +3 -3
  160. package/dist/type-generator/database/generate.js.map +1 -1
  161. package/dist/type-generator/migration.js +2 -2
  162. package/dist/type-generator/migration.js.map +1 -1
  163. package/dist/type-generator/serving/server-file-extractor.js +3 -3
  164. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  165. package/dist/utils/is-plain-object.js +11 -0
  166. package/dist/utils/is-plain-object.js.map +1 -0
  167. package/docs/api/appkit/Class.AppKitError.md +2 -1
  168. package/docs/api/appkit/Class.AuthenticationError.md +1 -1
  169. package/docs/api/appkit/Class.ConfigurationError.md +1 -1
  170. package/docs/api/appkit/Class.ConnectionError.md +1 -1
  171. package/docs/api/appkit/Class.DatabaseValidationError.md +1 -1
  172. package/docs/api/appkit/Class.ExecutionError.md +1 -1
  173. package/docs/api/appkit/Class.IdentityExpiredError.md +190 -0
  174. package/docs/api/appkit/Class.InitializationError.md +1 -1
  175. package/docs/api/appkit/Class.Plugin.md +8 -12
  176. package/docs/api/appkit/Class.ServerError.md +1 -1
  177. package/docs/api/appkit/Class.ServiceContext.md +169 -0
  178. package/docs/api/appkit/Class.TunnelError.md +1 -1
  179. package/docs/api/appkit/Class.ValidationError.md +1 -1
  180. package/docs/api/appkit/Function.createApp.md +12 -12
  181. package/docs/api/appkit/Function.getCallerContext.md +12 -0
  182. package/docs/api/appkit/Function.getCurrentUserId.md +14 -0
  183. package/docs/api/appkit/Function.getExecutionContext.md +1 -1
  184. package/docs/api/appkit/Function.getUserContext.md +16 -0
  185. package/docs/api/appkit/Function.isInUserContext.md +12 -0
  186. package/docs/api/appkit/Function.isUserContext.md +20 -0
  187. package/docs/api/appkit/Function.runAgent.md +1 -1
  188. package/docs/api/appkit/Function.runInCallerContext.md +27 -0
  189. package/docs/api/appkit/Function.runInUserContext.md +29 -0
  190. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  191. package/docs/api/appkit/Interface.AgentsPluginConfig.md +11 -0
  192. package/docs/api/appkit/Interface.CallerContext.md +13 -0
  193. package/docs/api/appkit/Interface.IndexConfig.md +1 -1
  194. package/docs/api/appkit/Interface.PluginManifest.md +9 -1
  195. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  196. package/docs/api/appkit/Interface.RunAgentInput.md +45 -1
  197. package/docs/api/appkit/TypeAlias.AgentAuth.md +8 -0
  198. package/docs/api/appkit/TypeAlias.AppKitApi.md +35 -0
  199. package/docs/api/appkit/TypeAlias.ExecutionContext.md +4 -1
  200. package/docs/api/appkit/TypeAlias.ExecutionResult.md +65 -0
  201. package/docs/api/appkit/TypeAlias.ScopedPluginMap.md +12 -0
  202. package/docs/api/appkit/TypeAlias.UserContext.md +109 -0
  203. package/docs/api/appkit/TypeAlias.UserScopedApp.md +39 -0
  204. package/docs/api/appkit.md +14 -0
  205. package/docs/plugins/agents.md +45 -0
  206. package/docs/plugins/execution-context.md +101 -50
  207. package/docs/plugins/lakebase.md +6 -82
  208. package/llms.txt +15 -1
  209. package/package.json +1 -1
  210. package/sbom.cdx.json +1 -1
@@ -21,12 +21,14 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
21
21
  | [DatabaseValidationError](./docs/api/appkit/Class.DatabaseValidationError.md) | Deliberate validation failure raised by a database mutation hook. Generated routes answer `422` and echo only the issues naming a public column; every other failure raised inside a hook stays an opaque server error. |
22
22
  | [DatabricksAdapter](./docs/api/appkit/Class.DatabricksAdapter.md) | Adapter that talks directly to Databricks Model Serving `/invocations` endpoint. |
23
23
  | [ExecutionError](./docs/api/appkit/Class.ExecutionError.md) | Error thrown when an operation execution fails. Use for statement failures, canceled operations, or unexpected states. |
24
+ | [IdentityExpiredError](./docs/api/appkit/Class.IdentityExpiredError.md) | The downstream service rejected the active caller's credentials. |
24
25
  | [InitializationError](./docs/api/appkit/Class.InitializationError.md) | Error thrown when a service or component is not properly initialized. Use when accessing services before they are ready. |
25
26
  | [MlflowClient](./docs/api/appkit/Class.MlflowClient.md) | A thin client over the Databricks workspace REST API, owning the host + bearer token so callers (eval-run creation, assessment writes, the judge's serving endpoint) don't each re-derive URLs or re-attach auth. The host is normalized once at construction. |
26
27
  | [Plugin](./docs/api/appkit/Class.Plugin.md) | Base abstract class for creating AppKit plugins. |
27
28
  | [PolicyDeniedError](./docs/api/appkit/Class.PolicyDeniedError.md) | Thrown when a policy denies an action. |
28
29
  | [ResourceRegistry](./docs/api/appkit/Class.ResourceRegistry.md) | Central registry for tracking plugin resource requirements. Deduplication uses type + resourceKey (machine-stable); alias is for display only. |
29
30
  | [ServerError](./docs/api/appkit/Class.ServerError.md) | Error thrown when server lifecycle operations fail. Use for server start/stop issues, configuration conflicts, etc. |
31
+ | [ServiceContext](./docs/api/appkit/Class.ServiceContext.md) | ServiceContext is a singleton that manages the service principal's WorkspaceClient and workspace ID. WarehouseResource owns warehouse bindings. |
30
32
  | [SupervisorApiAdapter](./docs/api/appkit/Class.SupervisorApiAdapter.md) | Adapter that calls the Databricks AI Gateway Responses API (`/ai-gateway/mlflow/v1/responses`). |
31
33
  | [TunnelError](./docs/api/appkit/Class.TunnelError.md) | Error thrown when remote tunnel operations fail. Use for tunnel connection issues, message parsing failures, etc. |
32
34
  | [ValidationError](./docs/api/appkit/Class.ValidationError.md) | Error thrown when input validation fails. Use for invalid parameters, missing required fields, or type mismatches. |
@@ -135,10 +137,12 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
135
137
 
136
138
  | Type Alias | Description |
137
139
  | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | [AgentAuth](./docs/api/appkit/TypeAlias.AgentAuth.md) | Identity an agent runs under. The only value is on-behalf-of-user. |
138
141
  | [AgentEvent](./docs/api/appkit/TypeAlias.AgentEvent.md) | - |
139
142
  | [AgentTool](./docs/api/appkit/TypeAlias.AgentTool.md) | Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP tools (`mcpServer()` / raw hosted), toolkit references from plugins (`analytics().toolkit()`), or adapter-hosted Supervisor-API tools (`supervisorTools.*`). |
140
143
  | [AgentTools](./docs/api/appkit/TypeAlias.AgentTools.md) | Per-agent tool record. String keys map to inline tools, toolkit entries, hosted tools, etc. |
141
144
  | [AgentToolsFn](./docs/api/appkit/TypeAlias.AgentToolsFn.md) | Function form of `AgentDefinition.tools`. Receives the typed [Plugins](./docs/api/appkit/TypeAlias.Plugins.md) map and returns a tool record. Invoked exactly once at setup (or once per `runAgent` call in standalone mode); the result is cached as the agent's resolved tool record. |
145
+ | [AppKitApi](./docs/api/appkit/TypeAlias.AppKitApi.md) | App instance with plugin exports and an explicit caller-scoped entry point. |
142
146
  | [BaseSystemPromptOption](./docs/api/appkit/TypeAlias.BaseSystemPromptOption.md) | - |
143
147
  | [CallerPrincipal](./docs/api/appkit/TypeAlias.CallerPrincipal.md) | The caller identity whose permissions authorize execution, not its resources. |
144
148
  | [ConfigSchema](./docs/api/appkit/TypeAlias.ConfigSchema.md) | Configuration schema definition for plugin config. Re-exported from the standard JSON Schema Draft 7 types. |
@@ -163,6 +167,7 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
163
167
  | [ResolvedToolEntry](./docs/api/appkit/TypeAlias.ResolvedToolEntry.md) | Internal tool-index entry after a tool record has been resolved to a dispatchable form. |
164
168
  | [ResourceFieldEntry](./docs/api/appkit/TypeAlias.ResourceFieldEntry.md) | - |
165
169
  | [ResourcePermission](./docs/api/appkit/TypeAlias.ResourcePermission.md) | Union of all possible permission levels across all resource types. |
170
+ | [ScopedPluginMap](./docs/api/appkit/TypeAlias.ScopedPluginMap.md) | - |
166
171
  | [SearchFilters](./docs/api/appkit/TypeAlias.SearchFilters.md) | - |
167
172
  | [ServingFactory](./docs/api/appkit/TypeAlias.ServingFactory.md) | Factory function returned by `AppKit.serving`. |
168
173
  | [Severity](./docs/api/appkit/TypeAlias.Severity.md) | Whether an assertion fails the eval (`gate`) or is tracked only (`soft`). |
@@ -170,6 +175,8 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
170
175
  | [ToolRegistry](./docs/api/appkit/TypeAlias.ToolRegistry.md) | - |
171
176
  | [ToPlugin](./docs/api/appkit/TypeAlias.ToPlugin.md) | Factory function type returned by `toPlugin()`. Accepts optional config and returns a PluginData tuple. |
172
177
  | [TransactionClient](./docs/api/appkit/TypeAlias.TransactionClient.md) | Entity and SQL capabilities bound to one transaction. |
178
+ | [~~UserContext~~](./docs/api/appkit/TypeAlias.UserContext.md) | - |
179
+ | [UserScopedApp](./docs/api/appkit/TypeAlias.UserScopedApp.md) | - |
173
180
 
174
181
  ## Variables[​](#variables "Direct link to Variables")
175
182
 
@@ -226,13 +233,16 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
226
233
  | [fromSupervisorApi](./docs/api/appkit/Function.fromSupervisorApi.md) | Creates an [AgentAdapter](./docs/api/appkit/Interface.AgentAdapter.md) backed by the Databricks AI Gateway Responses API (`/ai-gateway/mlflow/v1/responses`). |
227
234
  | [functionToolToDefinition](./docs/api/appkit/Function.functionToolToDefinition.md) | - |
228
235
  | [generateDatabaseCredential](./docs/api/appkit/Function.generateDatabaseCredential.md) | Generate OAuth credentials for Postgres database connection using the proper Postgres API. |
236
+ | [getCallerContext](./docs/api/appkit/Function.getCallerContext.md) | Get the caller context if one is active, otherwise `undefined`. Unlike `getExecutionContext()`, this does not require `ServiceContext` to be initialized and never throws. |
229
237
  | [getCurrentActorId](./docs/api/appkit/Function.getCurrentActorId.md) | The initiating user in a caller scope; no user actor exists in service scope. |
230
238
  | [getCurrentPrincipalKey](./docs/api/appkit/Function.getCurrentPrincipalKey.md) | Get the principal key for future cache keying: `app` or `user:<id>`. |
239
+ | [~~getCurrentUserId~~](./docs/api/appkit/Function.getCurrentUserId.md) | - |
231
240
  | [getExecutionContext](./docs/api/appkit/Function.getExecutionContext.md) | Get the current execution context. |
232
241
  | [getLakebaseOrmConfig](./docs/api/appkit/Function.getLakebaseOrmConfig.md) | Get Lakebase connection configuration for ORMs that don't accept pg.Pool directly. |
233
242
  | [getLakebasePgConfig](./docs/api/appkit/Function.getLakebasePgConfig.md) | Get Lakebase connection configuration for PostgreSQL clients. |
234
243
  | [getPluginManifest](./docs/api/appkit/Function.getPluginManifest.md) | Loads and validates the manifest from a plugin constructor. Normalizes string type/permission to strict ResourceType/ResourcePermission. |
235
244
  | [getResourceRequirements](./docs/api/appkit/Function.getResourceRequirements.md) | Gets the resource requirements from a plugin's manifest. |
245
+ | [~~getUserContext~~](./docs/api/appkit/Function.getUserContext.md) | - |
236
246
  | [getUsernameWithApiLookup](./docs/api/appkit/Function.getUsernameWithApiLookup.md) | Resolves the PostgreSQL username for a Lakebase connection. |
237
247
  | [getWarehouseId](./docs/api/appkit/Function.getWarehouseId.md) | Get the configured SQL warehouse ID after app initialization. The warehouse is an app resource; SP and caller executions use the same binding. Deprecated user-context scopes retain support for explicit warehouse overrides. |
238
248
  | [getWorkspaceClient](./docs/api/appkit/Function.getWorkspaceClient.md) | Get workspace client from config or SDK default auth chain |
@@ -241,10 +251,12 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
241
251
  | [integer](./docs/api/appkit/Function.integer.md) | - |
242
252
  | [isFunctionTool](./docs/api/appkit/Function.isFunctionTool.md) | - |
243
253
  | [isHostedTool](./docs/api/appkit/Function.isHostedTool.md) | - |
254
+ | [isInUserContext](./docs/api/appkit/Function.isInUserContext.md) | Check if currently running in a user context. |
244
255
  | [isJudgeConfigured](./docs/api/appkit/Function.isJudgeConfigured.md) | - |
245
256
  | [isSQLTypeMarker](./docs/api/appkit/Function.isSQLTypeMarker.md) | Type guard to check if a value is a SQL type marker |
246
257
  | [isSupervisorTool](./docs/api/appkit/Function.isSupervisorTool.md) | Type guard for [HostedSupervisorTool](./docs/api/appkit/Interface.HostedSupervisorTool.md). Used by the agents plugin (`buildToolIndex`) and standalone `runAgent` (`classifyTool`) to route supervisor-hosted tools to the extensions payload rather than the adapter's `tools` array. |
247
258
  | [isToolkitEntry](./docs/api/appkit/Function.isToolkitEntry.md) | Type guard for `ToolkitEntry` — used by the agents plugin to differentiate toolkit references from inline tools in a mixed `tools` record. |
259
+ | [~~isUserContext~~](./docs/api/appkit/Function.isUserContext.md) | - |
248
260
  | [jsonb](./docs/api/appkit/Function.jsonb.md) | - |
249
261
  | [loadAgentFromFile](./docs/api/appkit/Function.loadAgentFromFile.md) | Loads a single markdown agent file and resolves its frontmatter against registered plugin toolkits + ambient tool library. |
250
262
  | [loadAgentsFromDir](./docs/api/appkit/Function.loadAgentsFromDir.md) | Scans a directory for one subdirectory per agent, each containing `agent.md` (frontmatter + body). Produces an `AgentDefinition` record keyed by agent id (folder name). Throws on frontmatter errors or unresolved references. Returns an empty map if the directory does not exist. |
@@ -261,6 +273,8 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
261
273
  | [runAgent](./docs/api/appkit/Function.runAgent.md) | Standalone agent execution without `createApp`. Resolves the adapter, binds inline tools, and drives the adapter's `run()` loop to completion. |
262
274
  | [runEval](./docs/api/appkit/Function.runEval.md) | Run a single eval against a driver. Never throws for assertion or agent failures — those become a non-passing [EvalResult](./docs/api/appkit/Interface.EvalResult.md). Only a malformed eval definition surfaces as `result.error`. |
263
275
  | [runEvalsInDir](./docs/api/appkit/Function.runEvalsInDir.md) | Discover, load, and run every eval under each agent's `evals/` dir, driving the agents on a running app. Never throws for an individual eval — load/run failures become non-passing [EvalResult](./docs/api/appkit/Interface.EvalResult.md)s. |
276
+ | [runInCallerContext](./docs/api/appkit/Function.runInCallerContext.md) | Run a function with an immutable snapshot of the caller context. Nested and concurrent scopes keep their own identities. |
277
+ | [~~runInUserContext~~](./docs/api/appkit/Function.runInUserContext.md) | - |
264
278
  | [runWithRetries](./docs/api/appkit/Function.runWithRetries.md) | Run `attempt` up to `1 + retries` times, stopping as soon as it returns a result that is neither a thrown error / per-eval timeout (`error`) nor a transport/agent turn failure (`infraFailure`). Assertion failures set neither, so a failed-but-completed eval is returned on the first try and never retried. Returns the last result when every attempt failed on infra. |
265
279
  | [summarize](./docs/api/appkit/Function.summarize.md) | - |
266
280
  | [text](./docs/api/appkit/Function.text.md) | - |
@@ -326,6 +326,49 @@ const result = await runAgent(classifier, {
326
326
 
327
327
  MCP hosted tools (`mcpServer(...)`) still require `agents()` (they need a live MCP client). Supervisor-API hosted tools (`supervisorTools.*`), by contrast, **work in standalone `runAgent`** — the adapter has everything it needs to execute them server-side. This makes batch-eval / CI use of supervisor agents possible without `createApp`. Plugin tool dispatch in standalone mode runs as the service principal (no OBO) and **bypasses the agents-plugin approval gate** — treat standalone runAgent as a trusted-prompt environment (CI, batch eval, internal scripts), not as an exposed user-facing surface.
328
328
 
329
+ ## Execution identity[​](#execution-identity "Direct link to Execution identity")
330
+
331
+ By default an agent runs **mixed**: the model call and hand-rolled `tool({ execute })` tools run as the app's service principal, and plugin-toolkit tools run as the requesting user. Set `auth: "on-behalf-of-user"` to run the whole agent as the user:
332
+
333
+ ```ts
334
+ agents({ auth: "on-behalf-of-user" }); // default for every agent
335
+ createAgent({ instructions: "...", auth: "on-behalf-of-user" }); // one agent
336
+
337
+ ```
338
+
339
+ In markdown, set `auth: on-behalf-of-user` in the frontmatter. A per-agent value overrides the plugin default. Omitting `auth` keeps the mixed behavior; there is no all-service-principal mode.
340
+
341
+ | Piece | Default (mixed) | `on-behalf-of-user` |
342
+ | ------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
343
+ | Model call | service principal | user |
344
+ | Plugin-toolkit tools | user | user |
345
+ | Hand-rolled `tool({ execute })` | service principal | user |
346
+ | Sub-agents | own mode | own mode, never service principal |
347
+ | Standalone `runAgent` | service principal, or user tools with `caller` | requires `caller` (or an ambient user scope); model and tools as user |
348
+ | MLflow tracing | service principal | service principal (exception) |
349
+ | Thread store | service principal | service principal (exception) |
350
+ | Catalog skills volume | service principal | service principal (exception) |
351
+ | Missing user token | plugin tools reject; the rest runs as the service principal | the request is rejected with 401 before any model or tool call |
352
+
353
+ An on-behalf-of-user agent fails closed:
354
+
355
+ * No forwarded user token: `401` before any model or tool call, in production and in development. There is no service-principal fallback.
356
+ * A `401` from the model mid-run becomes an `IDENTITY_EXPIRED` error event and the stream ends. The run is not retried as the service principal.
357
+ * A sub-agent never widens: under an on-behalf-of-user parent, a mixed sub-agent also runs as the user.
358
+
359
+ **Exceptions.** MLflow tracing, the thread store, and catalog skills (see [Catalog skills](#catalog-skills-unity-catalog-volume)) are app-owned and stay service principal in every mode. Thread rows are keyed by the user id.
360
+
361
+ **Pre-built adapters.** The user's client is applied when AppKit builds the adapter from a model string (`model: "my-endpoint"`, `defaultModel`, or `DATABRICKS_SERVING_ENDPOINT_NAME`). If you build a `DatabricksAdapter` yourself with a fixed `workspaceClient`, an on-behalf-of-user agent throws at boot instead of running the model as the service principal. Pass a provider so the client resolves per call, or use a model string. Mixed agents accept fixed-client adapters as before:
362
+
363
+ ```ts
364
+ DatabricksAdapter.fromModelServing("my-endpoint", {
365
+ workspaceClient: () => getWorkspaceClient(),
366
+ });
367
+
368
+ ```
369
+
370
+ **Provisioning.** Each user needs the `model-serving` user API scope on the app, and `CAN_QUERY` on any custom serving endpoint the agent calls. Plugin tools still need their own scopes and grants as in mixed mode.
371
+
329
372
  ## Adding agents to an existing app[​](#adding-agents-to-an-existing-app "Direct link to Adding agents to an existing app")
330
373
 
331
374
  Already have an app and want to add agents? What you touch depends on the kind:
@@ -492,6 +535,7 @@ agents({
492
535
  agents?: Record<string, AgentDefinition>, // DEPRECATED — use server/agents/<id>/ discovery
493
536
  defaultAgent?: string,
494
537
  defaultModel?: AgentAdapter | Promise<AgentAdapter> | string,
538
+ auth?: "on-behalf-of-user", // default: mixed (see Execution identity)
495
539
  tools?: Record<string, AgentTool>,
496
540
  autoInheritTools?: boolean | { file?: boolean, code?: boolean },
497
541
  autoInheritSkills?: boolean | { file?: boolean, code?: boolean }, // default off
@@ -956,6 +1000,7 @@ Skip `--experiment` (and `MLFLOW_EXPERIMENT_ID`) to run evals purely locally wit
956
1000
  | `maxTokens` | number | Adapter max-token hint. |
957
1001
  | `generationParams` | object | Adapter generation params (e.g. `temperature`, `top_p`) passed through when AppKit builds the adapter. |
958
1002
  | `baseSystemPrompt` | false \| string | Per-agent override. `false` disables the AppKit base prompt. |
1003
+ | `auth` | string | `on-behalf-of-user` runs this agent as the user. Any other value throws at boot. See [Execution identity](#execution-identity). |
959
1004
  | `ephemeral` | boolean | If `true`, the thread created for a chat request against this agent is deleted from `ThreadStore` after the stream finishes. Use for stateless one-shot agents (e.g. autocomplete) so history does not accumulate or contaminate future calls. Defaults to `false`. |
960
1005
 
961
1006
  Unknown keys are logged and ignored. Invalid YAML and missing plugin/tool references throw at boot.
@@ -1,78 +1,129 @@
1
1
  # Execution context
2
2
 
3
- AppKit manages Databricks authentication via two contexts:
3
+ AppKit uses the service principal by default. Open a caller scope when an operation should use the requesting user's Databricks permissions.
4
4
 
5
- * **ServiceContext** (singleton): Initialized at app startup with service principal credentials
6
- * **ExecutionContext**: Determined at runtime - either service principal or user context
5
+ ## User-scoped operations[​](#user-scoped-operations "Direct link to User-scoped operations")
7
6
 
8
- ## Headers for user context[​](#headers-for-user-context "Direct link to Headers for user context")
7
+ Prefer a scope block for handlers that perform multiple operations:
9
8
 
10
- * `x-forwarded-user`: required in production; identifies the user
11
- * `x-forwarded-access-token`: required for user token passthrough
9
+ ```ts
10
+ const result = await appkit.asUser(req).run(async (kit) => {
11
+ const orders = await kit.analytics.query("orders");
12
+ return orders;
13
+ });
12
14
 
13
- ## Using `asUser(req)` for user-scoped operations[​](#using-asuserreq-for-user-scoped-operations "Direct link to using-asuserreq-for-user-scoped-operations")
15
+ // Shorthand for one operation:
16
+ await appkit.asUser(req).analytics.query("orders");
14
17
 
15
- The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
18
+ // With no caller scope, a plain call uses the service principal:
19
+ await appkit.analytics.query("metrics");
16
20
 
17
- ```ts
18
- // In a custom plugin route handler
19
- router.post("/users/me/data", async (req, res) => {
20
- // Execute as the user (uses their Databricks permissions)
21
- const result = await this.asUser(req).query("SELECT ...");
22
- res.json(result);
23
- });
21
+ ```
22
+
23
+ One immutable caller context is shared by the block and its plugin handles. Nested operations, tools, and plain app handles used inside the block inherit that context. Async work and lazy stream consumption preserve the caller. There is no public `asApp()` and scoped handles cannot chain another `asUser()`. Group principals are deferred.
24
+
25
+ The Apps proxy supplies `x-forwarded-access-token` and `x-forwarded-user`, both required in production. `x-forwarded-email` is optional. Only trust these headers behind the authenticated Apps proxy or your own trusted authentication boundary.
26
+
27
+ `plugin.asUser(req)` remains available with a one-time deprecation warning. New code should use `appkit.asUser(req)`.
28
+
29
+ ## Tools and agents[​](#tools-and-agents "Direct link to Tools and agents")
30
+
31
+ `PluginContext.executeTool` inherits an existing caller scope. Without one, it establishes user scope from the request, preserving the original fail-closed OBO default. Missing credentials reject before the provider executes. An existing caller takes precedence over conflicting or absent request credentials.
32
+
33
+ On the agents HTTP execution routes (`/invocations`, `/responses`, and `/api/agents/chat`), each plugin-toolkit tool call runs in user scope through `executeTool`, including tool calls made by sub-agents. A plugin tool call without usable user credentials rejects; it never runs as the service principal. The marked development fallback below is the only missing-token exception. The agent's model call and hand-rolled tools are not wrapped in user scope.
34
+
35
+ ## Which built-in surfaces run as the user[​](#which-built-in-surfaces-run-as-the-user "Direct link to Which built-in surfaces run as the user")
36
+
37
+ The default is the **service principal**. Work runs on behalf of the user only inside a caller scope: `appkit.asUser(req)`, a route that opens one, or a query file that selects the user lane. There is no global "OBO everywhere" mode, so identity is decided per surface:
38
+
39
+ | Surface | Runs as | Why |
40
+ | --------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
41
+ | Genie routes | signed-in user (OBO) | the built-in route runs every call in user scope |
42
+ | Serving plugin (deprecated) routes | signed-in user (OBO) | the built-in route runs every call in user scope; prefer the agents plugin |
43
+ | Analytics `.obo.sql` queries | signed-in user (OBO) | the `.obo.sql` file name selects the user lane |
44
+ | Analytics `.sql` queries, Files, and other plugin calls | app service principal | user only inside `appkit.asUser(req)`, or for Files volumes configured with `auth: "on-behalf-of-user"` |
45
+ | Agents HTTP routes: the model (LLM) call | app service principal | the model adapter builds its own service-principal client, and the route does not open user scope |
46
+ | Agents HTTP routes: plugin-toolkit tool calls (`plugin:<name>`) | signed-in user (OBO) | `executeTool` opens user scope for each call; without user credentials the call rejects |
47
+ | Agents HTTP routes: hand-rolled `tool({ execute })` | app service principal | `execute` receives only the validated arguments and runs in the app context, as before |
48
+ | Standalone `runAgent` (no HTTP request) | app service principal by default | there is no request, so no user scope unless you pass `caller` (see [Standalone agents](#standalone-agents)) |
49
+
50
+ So an agent's **model inference runs as the service principal**, while the tools it calls over the built-in HTTP routes run on behalf of the user. See the [agents plugin](./docs/plugins/agents.md) for the tool-level detail.
51
+
52
+ ## Context helpers and cache isolation[​](#context-helpers-and-cache-isolation "Direct link to Context helpers and cache isolation")
53
+
54
+ Exported from `@databricks/appkit`:
55
+
56
+ * `getCurrentPrincipalKey()`: `app` or `user:<id>`, used to partition cache entries.
57
+ * `getCurrentActorId()`: initiating user ID, when available, for audit and telemetry.
58
+ * `getWorkspaceClient()`: workspace client for the current execution.
59
+ * `getWarehouseId()`: resource configuration, separate from execution identity.
60
+ * `getWorkspaceId()`: workspace ID as a promise.
61
+
62
+ The old context helpers remain as deprecated compatibility aliases. Cache keys now include the principal namespace even when an explicit legacy user key is supplied. Existing stored entries will have a cold miss after upgrading; users and SP continue to have separate cache entries and in-flight work.
63
+
64
+ ## Standalone agents[​](#standalone-agents "Direct link to Standalone agents")
65
+
66
+ Standalone `runAgent` can opt into user execution without an HTTP request:
24
67
 
25
- // Service principal execution (default)
26
- router.post("/system/data", async (req, res) => {
27
- const result = await this.query("SELECT ...");
28
- res.json(result);
68
+ ```ts
69
+ await runAgent(agent, {
70
+ messages: "Summarize my data",
71
+ caller: {
72
+ token: userToken,
73
+ principal: { type: "user", userId },
74
+ host: "https://your-workspace.cloud.databricks.com",
75
+ workspaceId,
76
+ },
29
77
  });
30
78
 
31
79
  ```
32
80
 
33
- ## Which built-in surfaces run OBO vs service principal[​](#which-built-in-surfaces-run-obo-vs-service-principal "Direct link to Which built-in surfaces run OBO vs service principal")
81
+ Get credentials through a trusted authentication flow, never from model output. The caller applies to plugin initialization, model adapters, tools, and nested agents. Omitting it inherits an existing caller scope or defaults to SP. Invalid explicit credentials reject even in development. No service context or CLI profile is initialized implicitly. Standalone execution still has no approval gate and is intended for trusted scripts and evaluations.
34
82
 
35
- The default is the **service principal**: an operation runs on behalf of the user only when it goes through `asUser(req)`, which needs the forwarded user token. There is no global "OBO everywhere" mode, so identity is decided per surface:
83
+ ## Development fallback[​](#development-fallback "Direct link to Development fallback")
36
84
 
37
- | Surface | Runs as | Why |
38
- | ----------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
39
- | Genie routes | signed-in user (OBO) | the built-in route calls `asUser(req)` automatically |
40
- | Files / Analytics ops via `asUser(req)` | signed-in user (OBO) | service principal by default; OBO only when you wrap the call in `asUser(req)` |
41
- | Agents plugin `/chat` — the model (LLM) call | app service principal | the chat route does not call `asUser`, and the model adapter is built at startup with the service-principal client |
42
- | Agents plugin — plugin-toolkit tool calls (`plugin:<name>`) | signed-in user (OBO) | dispatched through `asUser(req)` per call |
43
- | Agents plugin — hand-rolled `tool({ execute })` | app service principal | receives only tool arguments, no `req`, so it can't opt into OBO |
44
- | Standalone `runAgent` (no HTTP request) | app service principal | no request context, so neither the model nor any tool runs OBO |
45
- | Serving plugin (deprecated) routes | signed-in user (OBO) | the built-in route calls `asUser(req)`; prefer the agents plugin |
85
+ With `NODE_ENV=development`, `asUser(req)` without a token logs a warning and runs with default app credentials, marked `DEV_OBO_FALLBACK`. If a caller scope is already open, fallback retains it instead of widening to SP. The marker does not leak outside the scope. Production never falls back when credentials are missing.
46
86
 
47
- So an agent's **model inference runs as the service principal**; only the plugin tools it calls over the built-in HTTP routes run on behalf of the user. See the [agents plugin](./docs/plugins/agents.md) for the tool-level detail.
87
+ ## App-only resources and missing credentials[​](#app-only-resources-and-missing-credentials "Direct link to App-only resources and missing credentials")
48
88
 
49
- ## Context helper functions[​](#context-helper-functions "Direct link to Context helper functions")
89
+ The manifest capability contract keeps `secret`, `database`, and `postgres` app-only for the new `appkit.asUser` and `runInCallerContext` APIs. Accessing those resources through these caller-scoped plugin APIs or tools raises a clear error identifying the resource by its manifest alias, or its type when no alias is available. The message states that the resource is app-only in this version of AppKit and does not support OBO execution through these APIs. The check applies when using cached handles inside a later user scope too. It does not reject unrelated plugins merely because an app-only plugin is installed. Required resources, runtime requirements, and configured optional resources determine the plugin's resource capability.
50
90
 
51
- Exported from `@databricks/appkit`:
91
+ Deprecated `plugin.asUser` and `runInUserContext`, direct request-based tool dispatch, and the existing agents HTTP routes retain their established resource behavior, including Lakebase per-user routing. They still establish user identity and reject missing production credentials; they never fall back to SP by omission. Using a deprecated entry point inside a new guarded scope cannot disable its guards.
52
92
 
53
- * `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
54
- * `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
55
- * `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
56
- * `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
93
+ Missing-token messages distinguish OBO-capable resources from generic operations. For an OBO-capable resource, the message explains that no user token was forwarded and the app may be deployed service-principal-only. Otherwise the generic missing user token error remains. The app-level check uses the registered plugins' active resource metadata because no individual plugin has been selected yet.
57
94
 
58
- ## Telemetry span attributes[​](#telemetry-span-attributes "Direct link to Telemetry span attributes")
95
+ ## Credential expiration and telemetry[​](#credential-expiration-and-telemetry "Direct link to Credential expiration and telemetry")
59
96
 
60
- The `plugin.execute` span created by the execution interceptor chain includes these attributes:
97
+ A structured downstream HTTP 401 inside a caller scope throws `IdentityExpiredError` with code `IDENTITY_EXPIRED`. It includes the existing token fingerprint, not the token or upstream credential-bearing error. Obtain fresh user credentials before retrying. Non-401 failures and SP execution keep their existing behavior. Plugin `execute()` preserves its failed-result envelope and adds the typed error in the optional `error` field; SSE streams expose `IDENTITY_EXPIRED` in the error payload's `errorCode` field.
61
98
 
62
- | Attribute | Type | Description |
63
- | ---------------------------- | ----------------------- | ---------------------------------------------------------------------------------- |
64
- | `execution.context` | `"user"` \| `"service"` | Whether the operation runs as a user (OBO) or service principal |
65
- | `caller.id` | `string` | The user ID (OBO) or service principal ID |
66
- | `execution.obo_dev_fallback` | `boolean` | Set to `true` when an OBO call falls back to service principal in development mode |
99
+ AppKit-managed spans include `appkit.execution.principal` (`app` or `user`) and `appkit.execution.principal_id` (user or SP ID, or `app` before initialization). `appkit.execution.actor_id` is present when an initiating user exists. Tokens are never attached to these attributes.
67
100
 
68
- These attributes are automatically added when your plugin uses `execute()` or `executeStream()`. All built-in plugins use these methods for their OBO operations. Custom plugins should do the same to get automatic telemetry instrumentation.
101
+ ## Real user execution locally[​](#real-user-execution-locally "Direct link to Real user execution locally")
69
102
 
70
- ## Lakebase per-user connections[​](#lakebase-per-user-connections "Direct link to Lakebase per-user connections")
103
+ Set `DATABRICKS_TOKEN` and `DATABRICKS_HOST` in the app's `.env` to use a user token directly:
71
104
 
72
- The Lakebase plugin uses a different mechanism for `asUser(req)`: instead of swapping the `WorkspaceClient` via AsyncLocalStorage, it creates a **separate `pg.Pool` per user**, each with its own OAuth token refresh. This is necessary because PostgreSQL connections are authenticated at connection time — the pool itself is the authentication boundary.
105
+ ```dotenv
106
+ DATABRICKS_HOST=https://your-workspace.cloud.databricks.com
107
+ DATABRICKS_TOKEN=your-user-token
108
+
109
+ ```
110
+
111
+ When `DATABRICKS_TOKEN` is present, it takes precedence. AppKit uses it directly and resolves the user ID from the configured host. Otherwise, set `DATABRICKS_CONFIG_PROFILE` to an authenticated user profile for the same workspace as the app. The generated template already sets the profile when you choose one during scaffolding:
112
+
113
+ ```dotenv
114
+ DATABRICKS_CONFIG_PROFILE=your-user-profile
115
+
116
+ ```
117
+
118
+ Then run your usual command:
119
+
120
+ ```sh
121
+ npm run dev
122
+
123
+ ```
73
124
 
74
- See [Lakebase plugin — per-user connections](./docs/plugins/lakebase.md#on-behalf-of-obo--per-user-connections) for details.
125
+ Open the app's normal localhost URL. In development, the server automatically adds `x-forwarded-access-token`, `x-forwarded-user`, and optional email headers before plugin routes and custom routes run. No separate proxy, target, or port is needed. `asUser(req)` uses that user identity. Unscoped operations still use the app's configured credentials; injecting headers does not open a caller scope. For a genuine SP-versus-user comparison, the app credentials must belong to an SP, not the same user profile.
75
126
 
76
- ## Development mode behavior[​](#development-mode-behavior "Direct link to Development mode behavior")
127
+ Credentials stay in memory and refresh after 30 seconds of use. Initial auth and refresh failures return 401, never a silent SP fallback. Existing forwarded user tokens are preserved. Automatic injection runs only in `NODE_ENV=development` and only for same-origin loopback requests, including `localhost`. Use it only with a trusted local app. It emulates user credentials, not platform consent, scope enforcement, or resource provisioning.
77
128
 
78
- In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and skips user impersonation — the operation runs with the default credentials configured for the app instead. The telemetry span will show `execution.context: "service"` with `execution.obo_dev_fallback: true` to distinguish these from regular service principal calls.
129
+ Set `APPKIT_DEV_OBO=false` in `.env` to disable automatic injection. Without a configured token or profile, injection is also disabled. Tokenless `asUser(req)` then keeps the existing `DEV_OBO_FALLBACK` behavior in development.
@@ -115,94 +115,18 @@ await createApp({
115
115
 
116
116
  ```
117
117
 
118
- ## On-Behalf-Of (OBO) — per-user connections[​](#on-behalf-of-obo--per-user-connections "Direct link to On-Behalf-Of (OBO) — per-user connections")
118
+ ## On-Behalf-Of (OBO)[​](#on-behalf-of-obo--per-user-connections "Direct link to On-Behalf-Of (OBO)")
119
119
 
120
- When your app needs Row-Level Security (RLS) or per-user data isolation, use `asUser(req)` to execute queries using a per-user Lakebase connection pool. Each user's pool is authenticated with their Databricks identity, so PostgreSQL's `current_user` reflects the actual user.
120
+ Lakebase is app-only through the new `appkit.asUser` and `runInCallerContext` APIs. Operations in those scopes fail with a clear error. For a connector call without a manifest alias:
121
121
 
122
- ### Prerequisites[​](#prerequisites-1 "Direct link to Prerequisites")
123
-
124
- 1. **Enable user authorization** in your Databricks App with the **`postgres`** scope. See [User authorization](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/auth#user-authorization) for setup instructions. In your `databricks.yml`:
125
-
126
- ```yaml
127
- resources:
128
- apps:
129
- app:
130
- user_api_scopes:
131
- - postgres
132
-
133
- ```
134
-
135
- Apps scaffolded with `databricks apps init` and the Lakebase plugin include this automatically.
136
-
137
- 2. Each app user needs a **Postgres role** in Lakebase. Create one with the Databricks CLI:
138
-
139
- ```bash
140
- databricks postgres create-role "projects/{project_id}/branches/{branch_id}" \
141
- --json '{"spec": {"identity_type": "USER", "postgres_role": "user@example.com"}}'
142
-
143
- ```
144
-
145
- Alternatively, create roles in the Lakebase UI under **Branch Overview** → **Add role**.
146
-
147
- note
148
-
149
- Do not grant `databricks_superuser` to OBO users — superusers bypass RLS. Use [fine-grained grants](#fine-grained-permissions) instead.
150
-
151
- ### Usage[​](#usage "Direct link to Usage")
152
-
153
- No configuration needed — just call `asUser(req)`:
154
-
155
- ```ts
156
- const AppKit = await createApp({
157
- plugins: [server(), lakebase()],
158
- });
159
-
160
- // Service principal query (default — bypasses RLS as table owner)
161
- const all = await AppKit.lakebase.query("SELECT * FROM app.orders");
162
-
163
- // User-scoped query (per-user pool, RLS enforced)
164
- app.get("/api/my-orders", async (req, res) => {
165
- const result = await AppKit.lakebase
166
- .asUser(req)
167
- .query("SELECT * FROM app.orders ORDER BY created_at DESC");
168
- res.json(result.rows);
169
- });
122
+ ```text
123
+ Resource "postgres" is app-only in this version of AppKit and does not support OBO (on-behalf-of-user) execution. It runs as the service principal. Resource type: postgres.
170
124
 
171
125
  ```
172
126
 
173
- When `asUser(req)` is called:
174
-
175
- 1. The user's token and identity are extracted from `x-forwarded-access-token` and `x-forwarded-email` headers (set automatically by Databricks Apps).
176
- 2. A per-user `pg.Pool` is created (or reused) with the user's OAuth credentials.
177
- 3. `query()` and `pool` use the user's pool — `current_user` in PostgreSQL reflects the user's identity.
178
-
179
- ### Row-Level Security example[​](#row-level-security-example "Direct link to Row-Level Security example")
180
-
181
- ```sql
182
- -- As the service principal (during app setup):
183
- ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;
184
-
185
- CREATE POLICY user_orders ON app.orders
186
- FOR ALL TO PUBLIC
187
- USING (owner = current_user);
188
-
189
- -- Grant access so OBO users can query
190
- GRANT USAGE ON SCHEMA app TO PUBLIC;
191
- GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA app TO PUBLIC;
192
-
193
- ```
194
-
195
- ### How it works[​](#how-it-works "Direct link to How it works")
196
-
197
- * The **service principal pool** (`AppKit.lakebase.pool`) is always created and used for DDL operations, seeding, and admin queries.
198
- * **Per-user pools** are created on the first `asUser(req)` call and cached by user identity. Each pool has its own OAuth token refresh cycle.
199
- * Idle connections within per-user pools close automatically (30s idle timeout). Empty pool objects are cleaned up periodically.
200
- * On shutdown, all pools (SP + user) are closed gracefully.
201
- * In development mode (`NODE_ENV=development`), if no user token is available, `asUser(req)` falls back to the SP pool with a warning.
202
-
203
- RLS and superusers
127
+ Use a plain Lakebase call outside a caller scope for SP execution. There is no `asApp()` escape from a caller scope. The guard also applies to agent tools, ORM configuration, and pool handles obtained before entering a new caller scope.
204
128
 
205
- PostgreSQL superusers bypass Row-Level Security entirely. Users with the `databricks_superuser` role will see all rows regardless of RLS policies. For RLS enforcement, use [fine-grained grants](#fine-grained-permissions) instead of the superuser role.
129
+ The platform has a `postgres` user API scope, but AppKit does not enable that capability in the new v1 execution API. For backward compatibility, deprecated `plugin.asUser(req)` and `runInUserContext` retain the existing per-user pool routing, as do direct request-based tool dispatch and existing agents routes. These paths keep the user's identity and do not silently use the SP. They cannot disable an enclosing new caller scope's guard. Existing applications can upgrade without migrating their Lakebase OBO calls, then adopt the new execution API explicitly. Database permissions and row-level policies still govern data access.
206
130
 
207
131
  ## Database Permissions[​](#database-permissions "Direct link to Database Permissions")
208
132