@dbx-tools/appkit-mastra 0.1.12 → 0.1.13

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
@@ -582,10 +582,10 @@ the moment the `lakebase` plugin is registered. Bare `mastra()` next to
582
582
  zero extra config required.
583
583
 
584
584
 
585
- | Knob | Default when `lakebase()` is registered | What it backs |
586
- | --------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
587
- | `storage` | **Per-agent** `PostgresStore` namespaced by `schemaName: "mastra_<agentId>"` so threads + messages stay isolated. | Mastra threads, messages, working memory. |
588
- | `memory` | **Shared singleton** `PgVector` across every agent (cross-agent semantic recall on one index). | RAG-style recall over past messages via FastEmbed vectors. |
585
+ | Knob | Default when `lakebase()` is registered | What it backs |
586
+ | --------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
587
+ | `storage` | **Per-agent** `PostgresStore` namespaced by `schemaName: "mastra_<agentId>"` so threads + messages stay isolated, plus a Mastra-instance-level `PostgresStore` in schema `mastra_instance`. | Mastra threads, messages, working memory; Mastra-instance-level workflow snapshots (`requireApproval` / `agent.resumeStream()`). |
588
+ | `memory` | **Shared singleton** `PgVector` across every agent (cross-agent semantic recall on one index). | RAG-style recall over past messages via FastEmbed vectors. |
589
589
 
590
590
 
591
591
  Override either at the plugin level, the agent level, or both. The agent
@@ -635,8 +635,15 @@ mastra({
635
635
  Notes:
636
636
 
637
637
  - `PostgresStore` runs `CREATE SCHEMA IF NOT EXISTS` on `init()`, so
638
- per-agent schemas spring into existence the first time an agent saves
639
- a message. No bundle / migration step required.
638
+ per-agent schemas (and the shared `mastra_instance` schema) spring
639
+ into existence the first time the agent saves anything. No bundle /
640
+ migration step required.
641
+ - The `mastra_instance` schema exists so that Mastra-instance-level
642
+ artifacts (workflow snapshots used by `agent.resumeStream()`,
643
+ `requireApproval` flows, etc.) live in their own namespace and never
644
+ collide with per-agent thread / message tables. Disable the instance
645
+ store by setting `storage: false` at plugin level - approval-gated
646
+ tool calls will then error on resume.
640
647
  - Disabling `lakebase()` from your plugin list while leaving `storage` /
641
648
  `memory` truthy fails fast at setup with a clear "lakebase plugin not
642
649
  registered" error.
@@ -14,6 +14,14 @@
14
14
  * index is almost always what users want; opt into per-agent recall
15
15
  * by passing a {@link MastraMemoryConfigOverride} on the agent.
16
16
  *
17
+ * Additionally, {@link MemoryBuilder.instanceStorage} returns a
18
+ * **Mastra-instance-level** `PostgresStore` (schema `mastra_instance`)
19
+ * used for workflow snapshots - the persistence layer
20
+ * `agent.resumeStream()` reads from when waking a suspended
21
+ * `requireApproval` tool call. Per-agent stores are not enough for
22
+ * this: workflow runs are scoped to the Mastra instance, not an
23
+ * individual agent's `Memory`.
24
+ *
17
25
  * Plugin-level `config.storage` / `config.memory` act as the baseline
18
26
  * (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
19
27
  * is registered); per-agent settings cascade on top of that.
@@ -21,6 +29,7 @@
21
29
  import { lakebase } from "@databricks/appkit";
22
30
  import { pluginUtils } from "@dbx-tools/appkit-shared";
23
31
  import { Memory } from "@mastra/memory";
32
+ import { PostgresStore } from "@mastra/pg";
24
33
  import type { MastraAgentDefinition } from "./agents.js";
25
34
  import type { MastraPluginConfig } from "./config.js";
26
35
  /** Pool handle returned by the AppKit `lakebase` plugin `exports().pool`. */
@@ -62,6 +71,18 @@ export declare class MemoryBuilder {
62
71
  * vector store enabled - Mastra accepts a missing `memory` field
63
72
  * and treats the agent as stateless.
64
73
  */
74
+ /**
75
+ * Build the Mastra-instance-level storage used for workflow
76
+ * snapshots. Returns `undefined` when plugin-level `storage` is
77
+ * disabled, in which case `agent.resumeStream()` (and therefore
78
+ * the `requireApproval` flow) will not be available.
79
+ *
80
+ * The store lives in a dedicated `mastra_instance` schema so it
81
+ * never collides with per-agent `mastra_<agentId>` namespaces.
82
+ * Workflow snapshots are not per-agent state; they belong to the
83
+ * `Mastra` instance that owns the workflow execution.
84
+ */
85
+ instanceStorage(): PostgresStore | undefined;
65
86
  forAgent(agentId: string, def: MastraAgentDefinition): Memory | undefined;
66
87
  private buildStorage;
67
88
  /**
@@ -14,6 +14,14 @@
14
14
  * index is almost always what users want; opt into per-agent recall
15
15
  * by passing a {@link MastraMemoryConfigOverride} on the agent.
16
16
  *
17
+ * Additionally, {@link MemoryBuilder.instanceStorage} returns a
18
+ * **Mastra-instance-level** `PostgresStore` (schema `mastra_instance`)
19
+ * used for workflow snapshots - the persistence layer
20
+ * `agent.resumeStream()` reads from when waking a suspended
21
+ * `requireApproval` tool call. Per-agent stores are not enough for
22
+ * this: workflow runs are scoped to the Mastra instance, not an
23
+ * individual agent's `Memory`.
24
+ *
17
25
  * Plugin-level `config.storage` / `config.memory` act as the baseline
18
26
  * (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
19
27
  * is registered); per-agent settings cascade on top of that.
@@ -76,6 +84,30 @@ export class MemoryBuilder {
76
84
  * vector store enabled - Mastra accepts a missing `memory` field
77
85
  * and treats the agent as stateless.
78
86
  */
87
+ /**
88
+ * Build the Mastra-instance-level storage used for workflow
89
+ * snapshots. Returns `undefined` when plugin-level `storage` is
90
+ * disabled, in which case `agent.resumeStream()` (and therefore
91
+ * the `requireApproval` flow) will not be available.
92
+ *
93
+ * The store lives in a dedicated `mastra_instance` schema so it
94
+ * never collides with per-agent `mastra_<agentId>` namespaces.
95
+ * Workflow snapshots are not per-agent state; they belong to the
96
+ * `Mastra` instance that owns the workflow execution.
97
+ */
98
+ instanceStorage() {
99
+ const setting = this.config.storage;
100
+ if (!setting)
101
+ return undefined;
102
+ if (typeof setting === "object") {
103
+ return new PostgresStore(withId(setting, "mastra-store__instance"));
104
+ }
105
+ return new PostgresStore({
106
+ id: "mastra-store__instance",
107
+ schemaName: "mastra_instance",
108
+ pool: this.requirePool(),
109
+ });
110
+ }
79
111
  forAgent(agentId, def) {
80
112
  const storageSetting = def.storage ?? this.config.storage;
81
113
  const memorySetting = def.memory ?? this.config.memory;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Mastra observability wiring for the `@dbx-tools/appkit-phoenix`
3
+ * sibling plugin.
4
+ *
5
+ * Mastra's `Observability` registry accepts any
6
+ * `@mastra/observability` `BaseExporter`. We use `OtelExporter` from
7
+ * `@mastra/otel-exporter` (Mastra's first-party OTLP shim) with the
8
+ * `custom` provider pointed at Phoenix's local collector URL. No
9
+ * Arize-specific wrapper is needed - Phoenix is a vanilla
10
+ * OpenInference-compatible OTLP/HTTP receiver.
11
+ *
12
+ * Discovery is structural so this module doesn't depend on
13
+ * `@dbx-tools/appkit-phoenix` at compile time: we look up the
14
+ * registered plugin by its registered name (`"phoenix"`) and read its
15
+ * `exports().collectorEndpoint()` if it is shaped like the phoenix
16
+ * plugin. The phoenix package is therefore an *optional* sibling -
17
+ * apps that don't install it just get an undefined observability
18
+ * config and Mastra runs without OTLP export.
19
+ */
20
+ import type { pluginUtils } from "@dbx-tools/appkit-shared";
21
+ import { Observability } from "@mastra/observability";
22
+ /**
23
+ * If the sibling `phoenix` plugin is registered AND has booted with a
24
+ * usable collector URL, return a Mastra `Observability` configured to
25
+ * stream traces + logs there. Otherwise return `undefined` so the
26
+ * caller can omit the field on the `new Mastra({...})` constructor.
27
+ *
28
+ * The exporter uses `provider.custom` with `http/protobuf`, which is
29
+ * what Phoenix's `/v1/traces` endpoint speaks natively. Switching
30
+ * Phoenix to gRPC would be a one-line `protocol: "grpc"` change and
31
+ * a different exported URL.
32
+ */
33
+ export declare function buildPhoenixObservability(context: pluginUtils.PluginContextLike | undefined, serviceName: string): Observability | undefined;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Mastra observability wiring for the `@dbx-tools/appkit-phoenix`
3
+ * sibling plugin.
4
+ *
5
+ * Mastra's `Observability` registry accepts any
6
+ * `@mastra/observability` `BaseExporter`. We use `OtelExporter` from
7
+ * `@mastra/otel-exporter` (Mastra's first-party OTLP shim) with the
8
+ * `custom` provider pointed at Phoenix's local collector URL. No
9
+ * Arize-specific wrapper is needed - Phoenix is a vanilla
10
+ * OpenInference-compatible OTLP/HTTP receiver.
11
+ *
12
+ * Discovery is structural so this module doesn't depend on
13
+ * `@dbx-tools/appkit-phoenix` at compile time: we look up the
14
+ * registered plugin by its registered name (`"phoenix"`) and read its
15
+ * `exports().collectorEndpoint()` if it is shaped like the phoenix
16
+ * plugin. The phoenix package is therefore an *optional* sibling -
17
+ * apps that don't install it just get an undefined observability
18
+ * config and Mastra runs without OTLP export.
19
+ */
20
+ import { Observability } from "@mastra/observability";
21
+ import { OtelExporter } from "@mastra/otel-exporter";
22
+ /** Plugin name the phoenix plugin registers under (matches `phoenix()`). */
23
+ const PHOENIX_PLUGIN_NAME = "phoenix";
24
+ /**
25
+ * If the sibling `phoenix` plugin is registered AND has booted with a
26
+ * usable collector URL, return a Mastra `Observability` configured to
27
+ * stream traces + logs there. Otherwise return `undefined` so the
28
+ * caller can omit the field on the `new Mastra({...})` constructor.
29
+ *
30
+ * The exporter uses `provider.custom` with `http/protobuf`, which is
31
+ * what Phoenix's `/v1/traces` endpoint speaks natively. Switching
32
+ * Phoenix to gRPC would be a one-line `protocol: "grpc"` change and
33
+ * a different exported URL.
34
+ */
35
+ export function buildPhoenixObservability(context, serviceName) {
36
+ const endpoint = readPhoenixEndpoint(context);
37
+ if (!endpoint)
38
+ return undefined;
39
+ return new Observability({
40
+ configs: {
41
+ phoenix: {
42
+ serviceName,
43
+ exporters: [
44
+ new OtelExporter({
45
+ provider: {
46
+ custom: {
47
+ endpoint,
48
+ protocol: "http/protobuf",
49
+ },
50
+ },
51
+ }),
52
+ ],
53
+ },
54
+ },
55
+ });
56
+ }
57
+ /**
58
+ * Pull the OTLP collector URL out of the registered `phoenix` plugin.
59
+ * Tolerant of the plugin being absent (returns `undefined`) and of a
60
+ * future shape change in its exports (anything that's not a string
61
+ * is ignored). The lookup is keyed off the registered plugin *name*
62
+ * so this file does not depend on `@dbx-tools/appkit-phoenix`.
63
+ */
64
+ function readPhoenixEndpoint(context) {
65
+ if (!context)
66
+ return undefined;
67
+ const plugin = context.getPlugins().get(PHOENIX_PLUGIN_NAME);
68
+ const exports_ = plugin?.exports?.();
69
+ const url = exports_?.collectorEndpoint?.();
70
+ return typeof url === "string" ? url : undefined;
71
+ }
@@ -34,6 +34,7 @@ import express from "express";
34
34
  import { buildAgents, FALLBACK_AGENT_ID } from "./agents.js";
35
35
  import { historyRoute } from "./history.js";
36
36
  import { createMemoryBuilder, needsLakebase } from "./memory.js";
37
+ import { buildPhoenixObservability } from "./observability.js";
37
38
  import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
38
39
  import { clearServingEndpointsCache, listServingEndpoints, resolveServingConfig, } from "./serving.js";
39
40
  const GENIE_MANIFEST = pluginUtils.data(genie).plugin.manifest;
@@ -236,7 +237,26 @@ export class MastraPlugin extends Plugin {
236
237
  // dev server. Since we're hosting Mastra inside our own Express
237
238
  // subapp via `@mastra/express`, custom routes must be passed to
238
239
  // the `MastraServer` constructor directly.
239
- this.mastra = new Mastra({ agents: this.built.agents });
240
+ //
241
+ // `storage` here is *Mastra-instance-level* and persists workflow
242
+ // snapshots (where suspended `requireApproval` tool calls live).
243
+ // It's separate from each agent's `Memory.storage`, which only
244
+ // covers thread / message history. Without it,
245
+ // `agent.resumeStream()` errors with "could not find a suspended
246
+ // run" and the approval UI hangs after the user clicks Approve.
247
+ const instanceStorage = memoryBuilder?.instanceStorage();
248
+ // Auto-wire OTLP trace export to the sibling `phoenix` plugin if
249
+ // it's registered. Returns undefined when phoenix isn't around so
250
+ // the field stays off the constructor and Mastra keeps its noop
251
+ // observability default. The serviceName is the plugin's bound
252
+ // name so multiple mastra instances in one process stay
253
+ // distinguishable in Phoenix.
254
+ const observability = buildPhoenixObservability(this.context, this.name);
255
+ this.mastra = new Mastra({
256
+ agents: this.built.agents,
257
+ ...(instanceStorage ? { storage: instanceStorage } : {}),
258
+ ...(observability ? { observability } : {}),
259
+ });
240
260
  this.mastraApp = express();
241
261
  attachRoutePatchMiddleware(this.mastraApp);
242
262
  this.mastraServer = new MastraServer(this.config, {
@@ -255,6 +275,8 @@ export class MastraPlugin extends Plugin {
255
275
  agents: Object.keys(this.built.agents),
256
276
  defaultAgent: this.built.defaultAgentId,
257
277
  routes: ["/route/chat", "/route/history", "/models"],
278
+ instanceStorage: instanceStorage !== undefined,
279
+ observability: observability !== undefined ? "phoenix" : "off",
258
280
  });
259
281
  }
260
282
  }