Repository navigation
feat(appkit): add on-behalf-of-user mode for agents - #631
Merged
Merged
Conversation
Add `auth: "on-behalf-of-user"` on agents({ auth }), createAgent({ auth })
and agent.md frontmatter. An OBO agent runs the model call, plugin tools,
hand-rolled tools and sub-agent dispatch as the user. Omitting it keeps
today's mixed behavior unchanged.
- DatabricksAdapter accepts a client provider resolved per call; agents
built from a model string use the caller's client in an OBO run.
- Routes reject an OBO request without a user token with 401 before any
model or tool call, then open the user scope once per request.
- Sub-agents use their own mode but never widen to the service principal
under an OBO parent. Standalone runAgent requires a caller.
- A model 401 mid-run surfaces as IDENTITY_EXPIRED with no SP retry.
- Thread-store writes and MLflow tracing stay service principal.
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Catalog skills are discovered at boot as the service principal and form a shared pool. Run read_skill_file outside the caller scope so an on-behalf-of-user agent cannot list a skill as the app and then fail to read it as the user. Document it as the third always-SP exception. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
An on-behalf-of-user agent whose DatabricksAdapter was built with a fixed workspaceClient would silently run the model as the service principal. Throw at boot instead and point to `workspaceClient: () => getWorkspaceClient()` or a model string. Adapters built from a model string or with a client provider, and mixed agents, are unaffected. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Contributor
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 1.2 MB (+36 KB) | 441 KB (+13 KB) |
| Type declarations | 454 KB (+8.3 KB) | 165 KB (+3.1 KB) |
| Source maps | 2.4 MB (+66 KB) | 826 KB (+24 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 4.1 MB (+111 KB) | 1.4 MB (+40 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
100 KB (+4.0 KB) | 2.5 KB (-1 B) | 103 KB (+4.0 KB) | external | 327 KB (+12 KB) |
./beta |
96 KB (+2.9 KB) | 485 B (+7 B) | 97 KB (+2.9 KB) | external | 291 KB (+8.7 KB) |
./testing |
41 KB (+3.0 KB) | 32 KB (+1.0 KB) | 73 KB (+4.0 KB) | external | 212 KB (+10 KB) |
./tsdown |
520 B | 0 B | 520 B | external | 813 B |
./type-generator |
23 KB | 0 B | 23 KB | external | 65 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 96 KB |
. |
utils.js |
initial | 4.6 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 79 KB |
./beta |
stream-manager.js |
initial | 5.9 KB |
./beta |
service-context.js |
initial | 4.2 KB |
./beta |
databricks.js |
initial | 3.3 KB |
./beta |
wide-event-emitter.js |
initial | 3.2 KB |
./beta |
client.js |
initial | 594 B |
./beta |
index.js |
initial | 20 B |
./beta |
supervisor-api.js |
lazy | 193 B |
./beta |
databricks.js |
lazy | 177 B |
./beta |
index.js |
lazy | 115 B |
./testing |
manifest.js |
initial | 28 KB |
./testing |
index.js |
initial | 10 KB |
./testing |
wide-event-emitter.js |
initial | 2.9 KB |
./testing |
index.js |
lazy | 28 KB |
./testing |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./testing |
utils.js |
lazy | 1.8 KB |
./tsdown |
index.js |
initial | 520 B |
./type-generator |
index.js |
initial | 23 KB |
@databricks/appkit-ui
npm tarball (packed): 350 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 395 KB | 132 KB |
| Type declarations | 229 KB | 84 KB |
| Source maps | 766 KB | 253 KB (+1 B) |
| CSS | 16 KB | 3.2 KB |
| Total | 1.4 MB | 472 KB (+1 B) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.3 KB | 49 KB | 55 KB | 208 KB | 14 KB |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
432 KB | 49 KB | 481 KB | 1.3 MB | 177 KB |
./react/beta |
1.0 KB | 0 B | 1.0 KB | 0 B | 1.9 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 5.2 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 20 B |
./react |
index.js |
initial | 430 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 1.0 KB |
Contributor
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 37492802585 -R databricks/appkit -n appkit-template-0.82.0-pr.5f6fbd7-feat-agents-auth-mode-631 -D appkit-pr-631 \
&& unzip -o "appkit-pr-631/appkit-template-0.82.0-pr.5f6fbd7-feat-agents-auth-mode-631.zip" -d "appkit-pr-631" \
&& databricks apps init --template "appkit-pr-631"The template pins |
MarioCadenas
added this pull request to stack #602
October 6, 2026 14:53
atilafassina
approved these changes
Oct 6, 2026
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #601.
Adds
auth: "on-behalf-of-user"to the agents plugin. You can set it as the default for every agent withagents({ auth }), or per agent withcreateAgent({ auth })orauth:in the agent.md frontmatter. If you leave it out, nothing changes: the model and hand-rolled tools run as the service principal, and plugin tools run as the user.Behavior by mode
on-behalf-of-usertool({ execute })runAgentcallercaller(or an ambient user scope)How it works
DatabricksAdapter(model serving and AI Gateway) now also accepts a client provider() => WorkspaceClientLike, resolved on every call. Agents that AppKit builds from a model string get a provider. In an OBO run it returns the caller's client; otherwise it returns the same eagerly built SP client as before.createRequestScope(req)once per request and set anAsyncLocalStoragemarker for the OBO run.read_skill_filerun through a new internalrunOutsideCallerScope, so they stay SP.normalizeIdentityError, so it surfaces asIDENTITY_EXPIREDand the run is not retried as the SP.auth: obothrows at boot. Otherwise a typo would silently fall back to mixed mode.Differences from the design doc
RunState. The ALS marker already gives sub-agents "never widen", and the adapter (built at setup) needs the marker anyway to pick its client.skillCredentialMode: "obo"is documented as not wired yet. Reading skills as the user would let the SP list a skill the user then can't read.workspaceClient: () => getWorkspaceClient().Tests
Each new test was checked to fail when its change is reverted. They cover the HTTP routes (
/invocations,/responses,/api/agents/chat), standalonerunAgent, the frontmatter loader, the adapter, and skill reads. The existing mixed-mode HTTP identity tests stay unchanged and act as the regression guard.Gates: build,
pnpm -r typecheck,pnpm test(5544 passed),pnpm docs:build, andpnpm checkall pass. The registry round-trip repro passes 20/20 against this build.This pull request and its description were written by Isaac.