@dbx-tools/appkit-mastra 0.1.112 → 0.3.1

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.
package/README.md CHANGED
@@ -1,44 +1,70 @@
1
1
  # @dbx-tools/appkit-mastra
2
2
 
3
- An AppKit plugin that hosts [Mastra](https://mastra.ai) agents inside a
4
- Databricks App with user-scoped workspace auth (OBO), optional
5
- Lakebase-backed memory, and the standard Mastra agent stream the React
6
- client drives via `@mastra/client-js`.
7
-
8
- Wiring it up looks the same as the AppKit
9
- `[agents](https://developers.databricks.com/docs/appkit/v0/plugins/agents)`
10
- plugin - same `createAgent` / `tool` helpers, same `tools(plugins)`
11
- callback, same `ToolkitOptions`. Switching a given agent between the two
12
- is a one-line import change.
13
-
14
- The implementation lives under `src/`: agent registration in
15
- `[agents.ts](src/agents.ts)`, the plugin + routes in
16
- `[plugin.ts](src/plugin.ts)` / `[server.ts](src/server.ts)`, thread
17
- history / listing in `[history.ts](src/history.ts)` /
18
- `[threads.ts](src/threads.ts)`, Model Serving resolution in
19
- `[model.ts](src/model.ts)` / `[serving.ts](src/serving.ts)`, Genie
20
- tooling in `[genie.ts](src/genie.ts)`, the chart pipeline in
21
- `[chart.ts](src/chart.ts)`, Databricks workspace mounts in
22
- `[workspaces.ts](src/workspaces.ts)` / `[filesystems.ts](src/filesystems.ts)`,
23
- and MLflow feedback logging in
24
- `[mlflow.ts](src/mlflow.ts)` (over the shared REST helper in
25
- `[rest.ts](src/rest.ts)`).
26
-
27
- ## Quick start
3
+ AppKit plugin and server-side toolkit for hosting Mastra agents inside a
4
+ Databricks App.
5
+
6
+ Import this package when an AppKit backend needs an agent service with
7
+ Databricks on-behalf-of auth, optional Lakebase-backed memory, Databricks Genie
8
+ tools, model selection, chart/data embeds, MLflow feedback, and MCP exposure.
9
+ The package mounts the standard Mastra agent stream under the AppKit server, so
10
+ clients can use Mastra-compatible chat transports instead of a custom protocol.
11
+
12
+ Key features:
13
+
14
+ - AppKit plugin lifecycle integration: routes, setup, shutdown, sibling plugin
15
+ access, and AppKit request context are handled inside `plugin.mastra()`.
16
+ - Agent composition: define one or more Mastra agents, give each one local tools,
17
+ AppKit plugin toolkits, workspace skills, model defaults, and approval-gated
18
+ tools.
19
+ - Databricks execution model: tool calls run with the active AppKit OBO client
20
+ where available, while storage and background work use service-principal
21
+ connections.
22
+ - Durable conversations: Lakebase-backed Mastra storage provides thread
23
+ history, message persistence, and optional vector memory.
24
+ - Rich data answers: Genie tools, statement fetches, chart preparation, and
25
+ embed markers let an agent answer with text plus delayed chart/table payloads.
26
+ - Operational surfaces: model-list routes, feedback routes, MCP exposure,
27
+ scoped API gating, tracing, and MLflow feedback are bundled with the plugin.
28
+
29
+ ## Why Not Just AppKit Agents?
30
+
31
+ Native AppKit includes a beta Agents plugin with markdown and TypeScript agent
32
+ definitions, AppKit tool-provider integration, streaming chat, thread
33
+ management, cancellation, and HITL approval. Use it when you want the AppKit
34
+ agent model and do not need a separate agent framework.
35
+
36
+ Use this package when you specifically want Mastra inside AppKit:
37
+
38
+ - Mastra's larger plugin/tool ecosystem, MCP support, memory/storage model,
39
+ workflow primitives, and `@mastra/client-js` stream shape.
40
+ - AppKit toolkits as Mastra tools, so Analytics, Files, Genie, and other AppKit
41
+ ToolProvider plugins stay available without rewriting them.
42
+ - Genie as an agent tool that emits typed progress events, result metadata, and
43
+ delayed chart/data markers into the same assistant turn.
44
+ - A paired React client in [`@dbx-tools/ui-mastra`](../../ui/mastra) with model
45
+ picking, thread sidebar, approvals, feedback, exports, and inline embeds.
46
+ - Per-request model override and fuzzy endpoint resolution through
47
+ [`@dbx-tools/model`](../model), instead of binding every agent to a fixed
48
+ endpoint name.
49
+
50
+ ## Quick Start
28
51
 
29
52
  ```ts
30
- import { analytics, createApp, files, lakebase, server } from "@databricks/appkit";
31
- import { createAgent, mastra, tool } from "@dbx-tools/appkit-mastra";
53
+ import { analytics, createApp, lakebase, server } from "@databricks/appkit";
54
+ import { agents, genie, plugin } from "@dbx-tools/appkit-mastra";
32
55
  import { z } from "zod";
33
56
 
34
- const support = createAgent({
35
- instructions: "You help customers with data and files.",
57
+ const analyst = agents.createAgent({
58
+ name: "analyst",
59
+ instructions: ["You answer questions about workspace data.", genie.GENIE_INSTRUCTIONS].join(
60
+ "\n\n",
61
+ ),
36
62
  tools(plugins) {
37
63
  return {
38
- ...plugins.analytics.toolkit(), // every analytics tool
39
- ...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
40
- get_weather: tool({
41
- description: "Weather",
64
+ ...plugins.analytics.toolkit(),
65
+ ...plugins.genie?.toolkit(),
66
+ get_weather: agents.tool({
67
+ description: "Get a simple weather report.",
42
68
  schema: z.object({ city: z.string() }),
43
69
  execute: async ({ city }) => `Sunny in ${city}`,
44
70
  }),
@@ -50,380 +76,319 @@ await createApp({
50
76
  plugins: [
51
77
  server(),
52
78
  analytics(),
53
- files(),
54
- // Drop `lakebase()` in and `mastra` auto-enables per-agent thread
55
- // storage plus shared semantic recall. Skip it for a stateless agent.
56
79
  lakebase(),
57
- mastra({ agents: support }),
80
+ plugin.mastra({
81
+ agents: { analyst },
82
+ defaultAgent: "analyst",
83
+ genie: { spaces: { sales: "01ef..." } },
84
+ }),
58
85
  ],
59
86
  });
60
87
  ```
61
88
 
62
- `createAgent` is a no-op identity helper that anchors type inference;
63
- `tool` is the AppKit-shaped factory (`{ description, schema, execute }`)
64
- that adapts to Mastra's `createTool` under the hood. The full config
65
- shapes (`MastraAgentDefinition`, the plugin config) - every field and
66
- default - are typed in `[config.ts](src/config.ts)`.
89
+ Benefits of importing the package:
90
+
91
+ - `plugin.mastra()` registers a full AppKit plugin named `mastra`.
92
+ - `agents.createAgent()` keeps agent definitions typed and applies the default
93
+ Databricks workspace/skill mounts.
94
+ - `agents.tool()` lets the same AppKit-shaped tool body work in this Mastra
95
+ plugin.
96
+ - `genie.GENIE_INSTRUCTIONS` and `plugins.genie.toolkit()` give agents a
97
+ Databricks Genie workflow without embedding a second agent.
98
+ - Lakebase registration automatically enables durable thread storage and vector
99
+ memory unless you opt out.
67
100
 
68
- On the React side, drop in the prebuilt chat UI from
69
- `[@dbx-tools/appkit-mastra-ui](../appkit-mastra-ui)`; it wires itself
70
- from the plugin's published client config and streams over
71
- `@mastra/client-js`, so there's no transport code to write:
101
+ ## Agent Registration
72
102
 
73
- ```tsx
74
- import { MastraChat } from "@dbx-tools/appkit-mastra-ui/react";
103
+ `plugin.mastra({ agents })` accepts a single definition, an array, or a record.
104
+ Records are best when clients need stable agent ids:
75
105
 
76
- export default function ChatPage() {
77
- return <MastraChat showModelPicker />;
78
- }
106
+ ```ts
107
+ plugin.mastra({
108
+ agents: {
109
+ support: agents.createAgent({ instructions: "Answer support questions." }),
110
+ analyst: agents.createAgent({ instructions: "Analyze workspace data." }),
111
+ },
112
+ defaultAgent: "support",
113
+ });
79
114
  ```
80
115
 
81
- ## Memory + storage
116
+ When no agents are supplied, the plugin registers a built-in `default` analyst so
117
+ the route surface still works for smoke tests. Each agent is streamed through the
118
+ Mastra agent API mounted below the plugin path, typically `/api/mastra`.
82
119
 
83
- With no config, memory and storage are driven entirely by whether the
84
- `lakebase` plugin is registered:
120
+ Use `agents.createTool` when you need Mastra-native fields such as
121
+ `outputSchema`, `suspendSchema`, `requireApproval`, or MCP metadata. Use
122
+ `agents.tool` for the smaller AppKit-compatible shape:
85
123
 
86
- - **No** `lakebase()` - the agent is fully stateless: no threads, no
87
- recall.
88
- - `lakebase()` **registered** - both auto-enable. Each agent gets its
89
- own `PostgresStore` schema (threads stay isolated per agent); every
90
- agent shares one `PgVector` semantic-recall index.
124
+ ```ts
125
+ const approveRefund = agents.createTool({
126
+ id: "approve_refund",
127
+ description: "Approve a refund request.",
128
+ inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
129
+ requireApproval: true,
130
+ execute: async ({ context }) => approve(context.orderId, context.amount),
131
+ });
132
+ ```
91
133
 
92
- Override per plugin or per agent by passing `memory` / `storage` as
93
- `false` (opt out), `true` (explicit on), or a config object (dedicated
94
- index / schema). The fields are typed in `[config.ts](src/config.ts)`;
95
- the wiring is in `[memory.ts](src/memory.ts)`.
134
+ ## AppKit Toolkits
96
135
 
97
- ## Workspace and Assistant skills
136
+ The `tools(plugins)` callback receives a dynamic index of registered AppKit
137
+ tool-provider plugins. Each entry exposes `.toolkit(opts)` with AppKit-compatible
138
+ `prefix`, `only`, `except`, and `rename` options.
98
139
 
99
- Every `createAgent` call applies a Mastra `Workspace` by default via
100
- `createWorkspace()` (`[workspaces.ts](src/workspaces.ts)`). The
101
- workspace is resolved per request and backed by the OBO user's
102
- `WorkspaceClient` through `[filesystems.ts](src/filesystems.ts)`.
140
+ ```ts
141
+ const agent = agents.createAgent({
142
+ instructions: "Use the narrowest tool that answers the question.",
143
+ tools(plugins) {
144
+ return {
145
+ ...plugins.analytics.toolkit({ only: ["query"] }),
146
+ ...plugins.files?.toolkit({ prefix: "files.", except: ["delete"] }),
147
+ };
148
+ },
149
+ });
150
+ ```
103
151
 
104
- Unless you opt out, `assistantSkills: true` mounts read-only Databricks
105
- trees where Mastra looks for `SKILL.md` files:
152
+ Tool calls dispatch back through the owning AppKit plugin, preserving OBO auth
153
+ and AppKit telemetry behavior. Optional plugins should be guarded with `?.` when
154
+ you spread their tools.
106
155
 
107
- | Databricks path | Workspace mount |
108
- | ---------------------------------- | ------------------------ |
109
- | `/Workspace/.assistant/skills` | `/workspace_skills` |
110
- | `/Users/<email>/.assistant/skills` | `/workspace_user_skills` |
156
+ ## Memory And Storage
111
157
 
112
- Production mounts require `workspace` or `all-apis` on the forwarded
113
- access token. `[server.ts](src/server.ts)` stamps parsed scopes on
114
- `MASTRA_SCOPES_KEY` using `tokenUtils` from `@dbx-tools/shared`.
115
- `NODE_ENV=development` skips the scope gate.
158
+ The `memory` and `storage` config fields can be `false`, `true`, or a concrete
159
+ Mastra Postgres/PgVector config.
116
160
 
117
161
  ```ts
118
- import { createAgent, createWorkspace } from "@dbx-tools/appkit-mastra";
162
+ plugin.mastra({
163
+ agents: analyst,
164
+ storage: true,
165
+ memory: { id: "analytics_memory", tableName: "agent_memory" },
166
+ });
167
+ ```
168
+
169
+ With `lakebase()` registered, both default to enabled:
170
+
171
+ - storage uses a per-agent schema for durable threads and messages;
172
+ - memory uses a shared vector index for semantic recall;
173
+ - the service-principal pool is created outside any request so OBO user
174
+ identities are not captured in background storage work.
175
+
176
+ Without `lakebase()`, agents are stateless unless you provide explicit storage
177
+ and memory configs.
178
+
179
+ ## Workspace Skills
119
180
 
120
- // Default: Assistant skills on.
121
- const support = createAgent({ instructions: "..." });
181
+ Every `agents.createAgent()` gets a default Mastra `Workspace` from
182
+ `workspaces.createWorkspace()`. It mounts Databricks Workspace files through the
183
+ current OBO user's `WorkspaceClient`, so Mastra can discover Assistant-style
184
+ `SKILL.md` files at request time.
122
185
 
123
- // Extra per-request mounts (merged with built-in skill mounts).
124
- const analyst = createAgent({
125
- instructions: "...",
126
- workspace: createWorkspace({
186
+ ```ts
187
+ const agent = agents.createAgent({
188
+ instructions: "Use mounted workspace skills when relevant.",
189
+ workspace: workspaces.createWorkspace({
190
+ assistantSkills: true,
127
191
  mounts: [
128
192
  async () => ({
129
- mounts: { "/data": myFilesystem },
130
- skillPaths: [],
193
+ mounts: { "/reference": myFilesystem },
194
+ skillPaths: ["/reference/skills"],
131
195
  }),
132
196
  ],
133
197
  }),
134
198
  });
135
-
136
- // Disable built-in skill mounts.
137
- const bare = createAgent({
138
- instructions: "...",
139
- workspace: createWorkspace({ assistantSkills: false }),
140
- });
141
- ```
142
-
143
- Exported for direct use: `createWorkspace`, `DatabricksWorkspaceFilesystem`,
144
- `emptyFilesystem`, and path helpers (`normalizeDatabricksBasePath`,
145
- `isDbfsPath`, `resolveDatabricksAbsolutePath`, ...).
146
-
147
- ## Conversations (threads)
148
-
149
- When storage is on, a user owns many conversation threads. The plugin
150
- resolves the thread a request targets from `RequestContext`, in order:
151
-
152
- 1. A **client-selected thread id** - the thread-selection header
153
- (`x-mastra-thread-id`) or `?threadId=` query. This is how the chat UI
154
- references a specific conversation: it picks an id from the threads
155
- listing (or mints one for a new chat) and stamps it on every call, so
156
- streaming, history, and clear all target that thread.
157
- 2. The **per-session cookie** (`appkit_<plugin-name>_session_id`), minted
158
- on first contact, as the single-thread fallback for clients that
159
- don't manage threads explicitly.
160
-
161
- The thread id pins both `agent.stream()` persistence and the history /
162
- threads routes, so client and server can never disagree on which
163
- conversation is active. Two custom routes back the UI's conversation
164
- management (registered alongside `/route/history`):
165
-
166
- - `GET /route/threads` (`/route/threads/:agentId`) - one page of the
167
- caller's threads, newest first, **scoped to the caller's resource** so
168
- a user only ever sees their own conversations.
169
- - `DELETE /route/threads` - delete the targeted thread (id from the
170
- thread-selection header). Ownership is enforced server-side: a thread
171
- is only removed when it belongs to the caller.
172
- - `PATCH /route/threads` - rename the targeted thread (id from the
173
- thread-selection header) to the `{ title }` in the JSON body. Same
174
- ownership enforcement; existing thread metadata is preserved.
175
-
176
- Each thread is auto-titled from its opening turn (Mastra's
177
- `generateTitle`), but titling runs on the **small / fast chat tier**
178
- (`ModelClass.ChatFast`) via the dedicated summarizer in
179
- `[summarize.ts](src/summarize.ts)` - not the agent's primary model - so
180
- naming a conversation never spends the heavyweight model. The route
181
- handlers live in `[threads.ts](src/threads.ts)`; the thread-id
182
- resolution is in `[server.ts](src/server.ts)`. The drop-in `MastraChat`
183
- from `@dbx-tools/appkit-mastra-ui` renders the whole conversation sidebar
184
- over these routes with no extra wiring.
185
-
186
- ### Summarization (`summarize` tool)
187
-
188
- Every agent gets a system-default ambient `summarize` tool that condenses
189
- arbitrary text (long content, notes, transcripts, bulky tool results) on
190
- the small / fast chat tier instead of the agent's primary model. It takes
191
- `text` plus optional `instructions` (e.g. "one sentence", "list the action
192
- items") and a `maxWords` soft cap, and returns `{ summary }`. The same
193
- small-tier summarizer backs conversation titling above. Shadow it by
194
- defining a same-named tool in `config.tools` (or a per-agent `tools`), and
195
- reuse it programmatically via the exported `summarizeText(config, text, opts)` / `buildSummarizeTool(config)`.
196
-
197
- ## The `tools(plugins)` callback
198
-
199
- Each agent supplies either a static `tools: { ... }` record or a
200
- `tools(plugins)` callback. The returned record accepts any tool shape
201
- Mastra understands - Mastra `createTool` tools, AppKit-shaped `tool()`
202
- tools, Vercel AI SDK tools, provider-defined tools, and toolkits from
203
- `plugins.<name>.toolkit(...)`.
204
-
205
- ```ts
206
- tools(plugins) {
207
- return {
208
- ...plugins.analytics.toolkit(), // every analytics tool
209
- ...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
210
- get_weather: tool({
211
- description: "Weather",
212
- schema: z.object({ city: z.string() }),
213
- execute: async ({ city }) => `Sunny in ${city}`,
214
- }),
215
- };
216
- }
217
199
  ```
218
200
 
219
- `plugins` is a runtime Proxy that auto-discovers any registered AppKit
220
- `ToolProvider` plugin (`analytics`, `files`, `lakebase`, `genie`, and
221
- any third-party plugin implementing the standard toolkit interface).
222
- Tool calls dispatch through the plugin's `executeAgentTool`, so OBO auth
223
- and telemetry spans stay intact. Plugins that aren't registered resolve
224
- to `undefined`, so guard optional backings with `?.` / `?? {}`.
225
-
226
- `toolkit(opts)` takes the same `ToolkitOptions` AppKit exposes
227
- (`prefix`, `only`, `except`, `rename`), passed through verbatim.
201
+ Production workspace mounts require a forwarded token with `workspace`,
202
+ `workspace.workspace`, or `all-apis` scope. Development mode skips that gate for
203
+ local iteration.
228
204
 
229
- ### `tool()` vs `createTool()`
205
+ ## Genie Tools
230
206
 
231
- `tool()` mirrors `@databricks/appkit/beta`'s shape so tool code is
232
- portable between the AppKit `agents` plugin and this one. Reach for
233
- Mastra's `createTool` when you need Mastra-only fields (`outputSchema`,
234
- `suspendSchema`, `requireApproval`, `mcp`, ...). An omitted `tool()` `id`
235
- is auto-derived from the description (slug + short hash) so traces stay
236
- stable across runs.
207
+ `genie.buildGenieTools()` and `plugins.genie.toolkit()` expose tools for:
237
208
 
238
- ## Genie
209
+ - asking a configured Genie space;
210
+ - reading space descriptions and serialized space metadata;
211
+ - fetching statement rows by `statement_id`;
212
+ - preparing charts from Genie result sets.
239
213
 
240
- `plugins.genie` returns a flat set of Mastra tools the central agent
241
- drives directly - no inner orchestrator agent. The agent asks the
242
- configured Genie space focused sub-questions, embeds inline markers in
243
- its prose, and the host UI resolves them (see [Embeds](#embeds)).
244
-
245
- The orchestration prompt ships as the exported `GENIE_INSTRUCTIONS`
246
- string; compose it into your agent's `instructions` to get the canonical
247
- behavior:
214
+ The central agent drives those tools directly. Genie events stream through the
215
+ Mastra writer using the shared contract from
216
+ [`@dbx-tools/shared-mastra`](../../shared/mastra), so clients can show thinking,
217
+ SQL, row counts, summaries, chart markers, and data markers as the turn runs.
248
218
 
249
219
  ```ts
250
- import { createAgent, GENIE_INSTRUCTIONS } from "@dbx-tools/appkit-mastra";
251
-
252
- const support = createAgent({
253
- instructions: `${baseInstructions}\n\n${GENIE_INSTRUCTIONS}`,
220
+ const agent = agents.createAgent({
221
+ instructions: `${baseInstructions}\n\n${genie.GENIE_INSTRUCTIONS}`,
254
222
  tools(plugins) {
255
- return { ...plugins.genie?.toolkit() };
223
+ return { ...plugins.genie?.toolkit({ prefix: "" }) };
256
224
  },
257
225
  });
258
226
  ```
259
227
 
260
- AppKit's stock `genie()` plugin is honored only for its `spaces` config
261
- (and the matching `app.yaml` resources); the tools talk to Genie
262
- directly via `@dbx-tools/genie` (`genieEventChat`) and the workspace
263
- `statementExecution` API. Each Genie turn forwards its wire
264
- `GenieChatEvent`s through `ctx.writer` for live UI progress. The exact
265
- tool set, ids, and writer-event contract live in `[genie.ts](src/genie.ts)`
266
- and the `GenieWriterEvent` docs in
267
- `[@dbx-tools/appkit-mastra-shared](../appkit-mastra-shared)`.
268
-
269
- ## Embeds
270
-
271
- Charts and data tables ride out-of-band via inline markers so the model
272
- never blocks on or round-trips large payloads:
273
-
274
- - `render_data` (a system-default ambient tool) and the Genie
275
- `prepare_chart` tool both mint a `chartId` synchronously, kick chart
276
- planning into the background, and return `{ chartId }` immediately. The
277
- model embeds `[chart:<chartId>]` on its own line where the chart should
278
- appear.
279
- - `[data:<statement_id>]` markers resolve to inline table data.
280
-
281
- The host UI splits the assistant text on these markers and long-polls
282
- the plugin's generic `${basePath}/embed/:type/:id` route until each
283
- entry settles. Cache entries carry a 1h TTL; after expiry the route
284
- 404s and the marker falls through harmlessly. Implementation and the
285
- placement contract are in `[chart.ts](src/chart.ts)`; the wire shapes
286
- are in `[@dbx-tools/appkit-mastra-shared](../appkit-mastra-shared)`.
287
-
288
- ## Feedback (MLflow)
289
-
290
- When MLflow tracing is wired for the deployment, the plugin lets users
291
- rate an assistant turn (thumbs up / down) and leave a comment, logged as
292
- a HUMAN [assessment](https://mlflow.org/docs/latest/genai/assessments/)
293
- on that turn's trace and attributed to the signed-in (OBO) user.
294
-
295
- Enablement is auto-detected: it turns on when an OTLP exporter endpoint
296
- (`OTEL_EXPORTER_OTLP_ENDPOINT` / `..._TRACES_ENDPOINT`) **and** an MLflow
297
- experiment (`MLFLOW_EXPERIMENT_ID` / `MLFLOW_EXPERIMENT_NAME`) are
298
- configured - the two signals that traces actually materialize in MLflow.
299
- Set `config.feedback` to `true` / `false` to force it on or off
300
- regardless of the env.
301
-
302
- ```ts
303
- mastra({ agents: support, feedback: true }); // force-enable
304
- ```
228
+ ## Charts And Data Embeds
305
229
 
306
- Under the hood the server derives MLflow's trace id from the active
307
- OpenTelemetry span (`tr-<hex(otelTraceId)>`) and stamps it on each
308
- turn's response (`MLFLOW_TRACE_ID_HEADER`); the client sends it back to
309
- `POST ${basePath}/route/feedback`, which posts the assessment to the
310
- Databricks MLflow REST API. Trace export is asynchronous, so a
311
- just-finished trace may not exist yet - the log call retries briefly on
312
- "not found" and otherwise fails softly (the chat stays usable). The
313
- current state is published to the client as
314
- `clientConfig().feedbackEnabled`; see `[mlflow.ts](src/mlflow.ts)` and
315
- the UI wiring in `[@dbx-tools/appkit-mastra-ui](../appkit-mastra-ui)`.
316
-
317
- ## Model resolution
318
-
319
- Each agent call resolves a model lazily (so concurrent requests keep
320
- distinct user identities), delegating to
321
- `[@dbx-tools/model](../model)` for the actual workspace-aware selection
322
- (fuzzy name matching, score-based class classification, offline
323
- fallbacks). Its exports are re-exported here, so `ModelClass` /
324
- `modelForClass` and friends are available straight off
325
- `@dbx-tools/appkit-mastra`.
326
-
327
- The plugin layers on the config sources for _which_ model an agent asks
328
- for, in priority order: a per-request override, the per-agent `model`,
329
- the plugin `defaultModel`, then `DATABRICKS_SERVING_ENDPOINT_NAME`. With
330
- none set, resolution falls through to the dynamic class picker.
230
+ `chart.prepareChart()` mints a chart id immediately, caches an in-progress
231
+ record, resolves the data in the background, and stores a terminal chart or
232
+ error. `chart.fetchChart()` long-polls that cache for route handlers and custom
233
+ clients.
331
234
 
332
235
  ```ts
333
- import { createAgent, ModelClass, modelForClass } from "@dbx-tools/appkit-mastra";
334
-
335
- const classifier = createAgent({
336
- instructions: "Classify this email into billing, support, or spam.",
337
- model: modelForClass(ModelClass.ChatFast),
236
+ const { chartId } = await chart.prepareChart({
237
+ config,
238
+ request: {
239
+ title: "Revenue by region",
240
+ chartType: "bar",
241
+ instructions: "Compare total revenue by region.",
242
+ data: rows,
243
+ },
244
+ resolveData: async () => rows,
338
245
  });
246
+
247
+ const resolved = await chart.fetchChart(chartId);
339
248
  ```
340
249
 
341
- Per-request overrides arrive via the `X-Mastra-Model` header, a
342
- `?model=` query, or a `model` / `modelId` body field (see
343
- `[serving.ts](src/serving.ts)`). The cached endpoint catalogue is
344
- exposed at `GET ${basePath}/models` for model pickers; the same data is
345
- available in-process from a sibling plugin via
346
- `appkitUtils.require(this.context, mastra).exports()`.
347
-
348
- ## MCP server
349
-
350
- The plugin's agents are exposed as a Mastra
351
- `[MCPServer](https://mastra.ai/docs/tools-mcp/mcp-overview)` **by
352
- default** so external MCP clients - Claude Desktop, Cursor, the Mastra
353
- playground, or another agent - can call them over the standard MCP
354
- transports. The server is registered on the Mastra instance via
355
- `mcpServers`, so `@mastra/express` serves the stock MCP routes under the
356
- plugin mount; no bespoke route is added. It's on out of the box because
357
- wrapping the already-registered agents is free - only the ambient tools
358
- (which assume an in-process chat turn) stay off unless opted in.
250
+ Agents can return `[chart:<id>]` and `[data:<statement_id>]` markers in prose.
251
+ The embed route resolves them later, which avoids forcing the language model to
252
+ inline large tables or wait for chart planning before continuing its answer.
359
253
 
360
- ```ts
361
- import { mastra } from "@dbx-tools/appkit-mastra";
254
+ ## Model Selection
362
255
 
363
- // MCP is on by default: every registered agent is an `ask_<agentId>`
364
- // MCP tool with no extra config.
365
- mastra({});
256
+ `model.buildModel()` adapts the generic resolver from
257
+ [`@dbx-tools/model`](../model) to Mastra. It resolves the model per request,
258
+ so OBO identity and request-specific overrides stay isolated.
366
259
 
367
- // Turn it off entirely.
368
- mastra({ mcp: false });
260
+ Model priority is:
369
261
 
370
- // Or tune the server id / metadata and expose extra tools.
371
- mastra({
372
- mcp: {
373
- serverId: "analytics",
374
- name: "Analytics MCP",
375
- version: "2.1.0",
376
- tools: true, // also expose ambient tools (off by default)
377
- },
262
+ 1. request override (`X-Mastra-Model`, `?model=`, body `model` / `modelId`);
263
+ 2. per-agent `model`;
264
+ 3. plugin `defaultModel`;
265
+ 4. `DATABRICKS_SERVING_ENDPOINT_NAME`;
266
+ 5. workspace catalogue ranking and static fallback floor.
267
+
268
+ ```ts
269
+ plugin.mastra({
270
+ agents: analyst,
271
+ defaultModel: "claude sonnet",
272
+ modelFuzzyMatch: true,
273
+ modelOverride: true,
378
274
  });
379
275
  ```
380
276
 
381
- With `mcp` enabled the transports mount at clean paths under the plugin
382
- base path (`/api/<plugin>`):
383
-
384
- - Streamable HTTP: `POST /api/<plugin>/mcp`
385
- - SSE (legacy): `GET /api/<plugin>/sse` + `POST /api/<plugin>/messages`
277
+ Use `serving.extractModelOverride()` and `serving.resolveServingConfig()` when
278
+ building custom routes that should behave like the plugin's `/models` and stream
279
+ routes.
386
280
 
387
- (`@mastra/express` mounts these under `/mcp/<serverId>/<transport>`
388
- internally - the plugin rewrites the clean alias to that route, so a
389
- client never sees the doubled `/mcp/<serverId>/mcp` segment.)
281
+ ## Threads, History, And Suggestions
390
282
 
391
- `serverId` defaults to the plugin name. Requests run under the same
392
- AppKit OBO scope as the chat routes, so an agent invoked over MCP
393
- resolves its model and tools as the calling user. The resolved endpoint
394
- paths are available in-process via
395
- `appkitUtils.require(this.context, mastra).exports().getMcp()`.
283
+ When storage is enabled, the plugin provides route helpers and in-process
284
+ functions for conversation management:
396
285
 
397
- ## Client API surface (`apiAccess`)
286
+ - `history.loadHistory()` and `history.clearHistory()` read or clear one thread;
287
+ - `threads.listThreads()`, `threads.renameThread()`, and
288
+ `threads.deleteThread()` operate on the caller's scoped conversations;
289
+ - `genie.collectSpaceSuggestions()` reads starter questions from the configured
290
+ Genie space.
398
291
 
399
- `@mastra/express` registers its full management route table under the
400
- plugin mount - agent inference _plus_ admin / mutating routes (direct
401
- tool execution, workflow control, raw memory read/write, telemetry,
402
- logs, scores). AppKit authenticates every request as the OBO user, but
403
- does not restrict _which_ of those a browser client may call.
292
+ The plugin resolves the active thread from `x-mastra-thread-id`, `?threadId=`,
293
+ or a per-session fallback cookie. That keeps streaming, history, and clear
294
+ operations aligned around the same conversation id.
404
295
 
405
- `config.apiAccess` gates that surface at the plugin's dispatch point:
296
+ ## Feedback And Observability
406
297
 
407
- - `"scoped"` (default): only the routes the chat client needs are
408
- dispatched to Mastra - agent inference (`stream` / `generate` /
409
- `network`), read-only agent metadata (`GET /agents`,
410
- `GET /agents/:id`), this plugin's own OBO- and resource-scoped
411
- `/route/*` routes (history / threads / feedback), and, when `mcp` is
412
- enabled, the MCP transport. Everything else is refused with `403`
413
- before it reaches Mastra.
414
- - `"full"`: dispatch the entire stock Mastra API. Use only for a trusted
415
- first-party console that genuinely needs the management surface.
298
+ `observability.buildObservability()` wires Mastra tracing when OTLP export is
299
+ configured. `mlflow.resolveFeedbackEnabled()` turns MLflow feedback on when both
300
+ trace export and an MLflow experiment are configured, unless the plugin config
301
+ forces a value.
416
302
 
417
303
  ```ts
418
- mastra({ agents: support }); // scoped by default
419
- mastra({ agents: support, apiAccess: "full" }); // opt into the full API
304
+ plugin.mastra({
305
+ agents: analyst,
306
+ feedback: true,
307
+ });
420
308
  ```
421
309
 
422
- The gate is a pure allowlist (`isMastraRequestAllowed` in
423
- `[server.ts](src/server.ts)`); the browser chat client only ever uses
424
- the agent stream and the `/route/*` routes, so the default breaks
425
- nothing.
310
+ `mlflow.logFeedback()` logs a human assessment against the active MLflow trace.
311
+ The response header name and request/response schemas live in
312
+ [`@dbx-tools/shared-mastra`](../../shared/mastra).
313
+
314
+ ## MCP Exposure
315
+
316
+ `mcp.buildMcpServer()` exposes registered agents as MCP tools by default. The
317
+ AppKit plugin publishes clean aliases under its base path:
426
318
 
427
- ## License
319
+ ```ts
320
+ plugin.mastra({
321
+ agents: analyst,
322
+ mcp: {
323
+ serverId: "analytics",
324
+ name: "Analytics MCP",
325
+ tools: false,
326
+ },
327
+ });
328
+ ```
428
329
 
429
- Apache-2.0
330
+ Use `mcp: false` to disable MCP. Turn on `tools: true` only for ambient tools
331
+ that are safe outside an in-process chat turn.
332
+
333
+ ## API Gate
334
+
335
+ The stock `@mastra/express` app has broad management routes. The plugin's
336
+ default `apiAccess: "scoped"` allows only the chat, read-only metadata,
337
+ plugin-owned `/route/*`, embed, model, suggestion, and MCP surfaces that the
338
+ client needs. Use `apiAccess: "full"` only for a trusted first-party console.
339
+
340
+ `server.isMastraRequestAllowed()` is exported for tests and custom dispatch
341
+ logic that need the same allowlist.
342
+
343
+ ## Configuration Reference
344
+
345
+ The plugin config is intentionally centered on the AppKit lifecycle instead of
346
+ requiring callers to assemble a Mastra server by hand.
347
+
348
+ - `agents` registers a single agent, an array, or a record keyed by stable agent
349
+ ids. Records are best for UIs because the ids become route-visible.
350
+ - `defaultAgent` controls which registered agent handles requests that do not
351
+ name an agent explicitly.
352
+ - `storage` and `memory` accept `true`, `false`, or concrete Mastra Postgres /
353
+ PgVector options. `true` resolves from `lakebase()` when present.
354
+ - `genie.spaces` maps aliases to Genie Space IDs. Those aliases flow into tools,
355
+ suggestions, and chart/data workflows.
356
+ - `defaultModel`, `modelOverride`, and `modelFuzzyMatch` control how loose model
357
+ names are resolved through Databricks Model Serving.
358
+ - `feedback` controls whether MLflow feedback routes are exposed. The automatic
359
+ mode enables feedback when tracing and an MLflow experiment are configured.
360
+ - `mcp` controls whether agents are exposed as MCP tools and how that server is
361
+ named.
362
+ - `apiAccess` chooses the route allowlist. Keep the default scoped mode for
363
+ deployed apps.
364
+
365
+ Use this package when you want an AppKit-native agent runtime. Use the shared
366
+ schemas in [`@dbx-tools/shared-mastra`](../../shared/mastra) when building a
367
+ client that talks to these routes.
368
+
369
+ ## Modules
370
+
371
+ - `plugin` - `MastraPlugin` and `mastra()` AppKit plugin factory.
372
+ - `agents` - `createAgent`, `tool`, `createTool`, agent build helpers, fallback
373
+ defaults, and approval-gated tool inspection.
374
+ - `config` - plugin config types and RequestContext key constants.
375
+ - `model` / `serving` / `servingSanitize` - Mastra model config, request
376
+ overrides, serving-endpoint config, and request-body cleanup.
377
+ - `genie` - Genie prompt, space normalization, Genie toolkits, and suggestions.
378
+ - `chart` / `statement` / `writer` - chart cache, statement row fetches, and
379
+ safe writer events.
380
+ - `history` / `threads` / `pagination` - conversation persistence helpers and
381
+ route handlers.
382
+ - `memory` / `storageSchema` - Lakebase-backed Mastra store/vector setup.
383
+ - `workspaces` / `filesystems` - Mastra workspace creation and Databricks
384
+ Workspace file adapters.
385
+ - `mcp` - MCP server construction.
386
+ - `observability` / `mlflow` - tracing and feedback.
387
+ - `server` / `rest` / `processors` - Express dispatch, Databricks REST helpers,
388
+ stream/result processors.
389
+
390
+ Browser-facing wire types are in
391
+ [`@dbx-tools/shared-mastra`](../../shared/mastra). Genie event contracts are in
392
+ [`@dbx-tools/shared-genie`](../../shared/genie). Model request/result contracts
393
+ are in [`@dbx-tools/shared-model`](../../shared/model). The matching React chat
394
+ surface is [`@dbx-tools/ui-mastra`](../../ui/mastra).