Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/agent-app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createAgent,
createApp,
files,
fromPlugin,
mcpServer,
server,
tool,
Expand All @@ -25,12 +26,14 @@ const get_weather = tool({

// Code-defined agent. Overrides config/agents/support.md if a file with that
// name exists. Tools here are explicit; defaults are strict (no auto-inherit
// for code-defined agents).
// for code-defined agents), so we pull analytics + files in via fromPlugin.
const support = createAgent({
instructions:
"You help customers with data analysis, file browsing, and general questions. " +
"Use the available tools as needed and summarize results concisely.",
tools: {
...fromPlugin(analytics),
...fromPlugin(files),
get_weather,
"mcp.vector-search": mcpServer(
"vector-search",
Expand Down
23 changes: 22 additions & 1 deletion apps/dev-playground/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import "reflect-metadata";
import {
agents,
analytics,
createAgent,
createApp,
files,
fromPlugin,
genie,
server,
tool,
} from "@databricks/appkit";
import { WorkspaceClient } from "@databricks/sdk-experimental";
import { z } from "zod";
import { lakebaseExamples } from "./lakebase-examples-plugin";
import { reconnect } from "./reconnect-plugin";
import { telemetryExamples } from "./telemetry-example-plugin";
Expand All @@ -22,6 +26,23 @@ function createMockClient() {
return client;
}

// Code-defined demo agent showing the fromPlugin() API alongside the
// markdown-driven agents in config/agents/.
const helper = createAgent({
instructions:
"You are a demo helper. Use analytics tools to answer data questions, " +
"or get_weather for light small-talk.",
tools: {
...fromPlugin(analytics),
get_weather: tool({
name: "get_weather",
description: "Get the current weather for a city",
schema: z.object({ city: z.string().describe("City name") }),
execute: async ({ city }) => `The weather in ${city} is sunny, 22°C`,
}),
},
});

createApp({
plugins: [
server({ autoStart: false }),
Expand All @@ -33,7 +54,7 @@ createApp({
}),
lakebaseExamples(),
files(),
agents(),
agents({ agents: { helper } }),
],
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
}).then((appkit) => {
Expand Down
63 changes: 62 additions & 1 deletion docs/docs/guides/migrating-to-agents-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,50 @@ You are a read-only data assistant.

Engineers declare tools in code; prompt authors pick from a menu in frontmatter. No YAML-as-code ceremony required.

## Scoping tools in code with `fromPlugin`

Earlier alphas of the new API required a three-touch dance for every plugin whose tools you wanted on a code-defined agent:

```ts
// Before: intermediate variable + .toolkit() + a second entry in plugins[]
const analyticsP = analytics();
const filesP = files();

const support = createAgent({
instructions: "…",
tools: {
...analyticsP.toolkit(),
...filesP.toolkit({ only: ["uploads.read"] }),
},
});

await createApp({
plugins: [server(), analyticsP, filesP, agents({ agents: { support } })],
});
```

`fromPlugin(factory, opts?)` collapses this to a single reference per plugin:

```ts
import { agents, analytics, createAgent, createApp, files, fromPlugin, server } from "@databricks/appkit";

const support = createAgent({
instructions: "…",
tools: {
...fromPlugin(analytics),
...fromPlugin(files, { only: ["uploads.read"] }),
},
});

await createApp({
plugins: [server(), analytics(), files(), agents({ agents: { support } })],
});
```

`fromPlugin` returns a spread-friendly, symbol-keyed marker. The agents plugin resolves it at setup against registered `ToolProvider`s and throws a clear `Available: …` error if the referenced plugin is missing from `plugins: [...]`.

`.toolkit()` is **not deprecated** — use it when you need to rename individual tools or combine fine-grained scoping that `fromPlugin`'s options can't express. For the 90% case where you want "all tools from this plugin", prefer `fromPlugin`.

## Standalone runs

The old `createAgent` returned a running HTTP app. Sometimes you want to run an agent in a script, cron, or test without HTTP. Use `runAgent`:
Expand All @@ -153,7 +197,24 @@ const result = await runAgent(classifier, { messages: "Billing issue please help
console.log(result.text);
```

Plugin toolkits (`ToolkitEntry` from `.toolkit()`) require `createApp`; `runAgent` throws a clear error if invoked with one.
To use plugin tools in standalone mode, pass the plugin factories through `plugins: [...]`. `runAgent` resolves any `fromPlugin` markers in the def against that list and dispatches tool calls as the service principal:

```ts
import { analytics, createAgent, fromPlugin, runAgent } from "@databricks/appkit";

const classifier = createAgent({
instructions: "Classify tickets. Use analytics.query for historical data.",
model: "databricks-claude-sonnet-4-5",
tools: { ...fromPlugin(analytics) },
});

await runAgent(classifier, {
messages: "is ticket 42 a duplicate?",
plugins: [analytics()],
});
```

Hosted/MCP tools are still `agents()`-only (they need the live MCP client). Raw `ToolkitEntry` spreads from `.toolkit()` can't be dispatched standalone — `runAgent` throws a clear error pointing you at `fromPlugin`.

## Gradual migration

Expand Down
55 changes: 46 additions & 9 deletions docs/docs/plugins/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,34 +75,54 @@ import {
createAgent,
createApp,
files,
fromPlugin,
server,
tool,
} from "@databricks/appkit";
import { z } from "zod";

const analyticsP = analytics();
const filesP = files();

const support = createAgent({
instructions: "You help customers with data and files.",
model: "databricks-claude-sonnet-4-5", // string sugar
model: "databricks-claude-sonnet-4-5", // string sugar
tools: {
...fromPlugin(analytics), // all analytics tools
...fromPlugin(files, { only: ["uploads.read"] }), // filtered subset
get_weather: tool({
name: "get_weather",
description: "Weather",
schema: z.object({ city: z.string() }),
execute: async ({ city }) => `Sunny in ${city}`,
}),
...analyticsP.toolkit(), // spread plugin tools
...filesP.toolkit({ only: ["uploads.read"] }), // filtered
},
});

await createApp({
plugins: [server(), analyticsP, filesP, agents({ agents: { support } })],
plugins: [server(), analytics(), files(), agents({ agents: { support } })],
});
```

Code-defined agents start with no tools by default. Spread `.toolkit()` outputs into `tools: { ... }` explicitly. The asymmetry (file: auto-inherit, code: strict) matches the personas: prompt authors want zero ceremony, engineers want no surprises.
Code-defined agents start with no tools by default. `fromPlugin(factory)` is the primary way to pull in a plugin's tools — it returns a spread-friendly marker that the agents plugin resolves against registered `ToolProvider`s at setup time. No intermediate variable, no duplicate `plugins: [analyticsP, filesP, ...]` dance: you write the factory reference once inside `fromPlugin` and again in `plugins: [...]`.

The asymmetry (file: auto-inherit, code: strict) matches the personas: prompt authors want zero ceremony, engineers want no surprises.

### Scoping tools in code

`fromPlugin(factory, opts?)` accepts the same `ToolkitOptions` as markdown frontmatter:

| Option | Example | Meaning |
|---|---|---|
| `only` | `{ only: ["query"] }` | Allowlist of local tool names |
| `except` | `{ except: ["legacy"] }` | Denylist of local tool names |
| `prefix` | `{ prefix: "" }` | Drop the `${pluginName}.` prefix |
| `rename` | `{ rename: { query: "q" } }` | Remap specific local names |

For plugins that don't expose a `.toolkit()` method (e.g., third-party `ToolProvider` plugins authored with plain `toPlugin`), `fromPlugin` falls back to walking `getAgentTools()` and synthesizing namespaced keys (`${pluginName}.${localName}`). The fallback respects `only` / `except` / `rename` / `prefix` the same way.

If a referenced plugin is not registered in `createApp({ plugins })`, the agents plugin throws at setup with an `Available: …` listing so you can fix the wiring before the first request.

### Using `.toolkit()` directly (advanced)

`.toolkit()` is still available on `analytics()`, `files()`, `genie()`, and `lakebase()` handles. Use it when you need to rename tools individually or bind them under a custom record key — anything `fromPlugin` can't express. In the common case, prefer `fromPlugin`.

## Level 4: sub-agents

Expand Down Expand Up @@ -156,7 +176,24 @@ for (const ticket of tickets) {
}
```

`runAgent` drives the adapter without `createApp` or HTTP. Limitation: plugin toolkits (`ToolkitEntry`) require a live `PluginContext`, so they only work when invoked through `agents()` + `createApp`. Inline `tool()` and `mcpServer()` both work standalone.
`runAgent` drives the adapter without `createApp` or HTTP. Inline `tool()` calls work standalone as shown above. To use plugin tools in standalone mode, pass the plugin factories through `RunAgentInput.plugins` — `runAgent` will resolve any `fromPlugin` markers in the def against that list:

```ts
import { analytics, createAgent, fromPlugin, runAgent } from "@databricks/appkit";

const classifier = createAgent({
instructions: "Classify tickets. Use analytics.query for historical data.",
model: "databricks-claude-sonnet-4-5",
tools: { ...fromPlugin(analytics) },
});

const result = await runAgent(classifier, {
messages: "is ticket 42 a duplicate?",
plugins: [analytics()],
});
```

Hosted tools (MCP) are still `agents()`-only since they require the live MCP client. Plugin tool dispatch in standalone mode runs as the service principal (no OBO) since there is no HTTP request.

## Configuration reference

Expand Down
Loading