eve 0.10.0 → 0.11.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.
- package/CHANGELOG.md +11 -0
- package/README.md +14 -0
- package/dist/src/cli/banner.d.ts +6 -3
- package/dist/src/cli/banner.js +1 -1
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/dev/tui/agent-header.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/evals/runner/run-evals.d.ts +1 -1
- package/dist/src/harness/input-requests.d.ts +20 -1
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/protocol/message.d.ts +11 -2
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/public/next/index.d.ts +1 -7
- package/dist/src/public/next/index.js +1 -1
- package/dist/src/public/next/vercel-output-config.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +27 -4
- package/dist/src/setup/scaffold/update/channels.js +1 -1
- package/docs/README.md +19 -5
- package/docs/agent-config.md +16 -19
- package/docs/channels/custom.mdx +24 -18
- package/docs/channels/discord.mdx +25 -13
- package/docs/channels/eve.mdx +24 -6
- package/docs/channels/github.mdx +41 -10
- package/docs/channels/linear.mdx +29 -36
- package/docs/channels/overview.mdx +16 -4
- package/docs/channels/slack.mdx +59 -32
- package/docs/channels/teams.mdx +19 -7
- package/docs/channels/telegram.mdx +23 -9
- package/docs/channels/twilio.mdx +28 -6
- package/docs/concepts/context-control.md +24 -41
- package/docs/concepts/default-harness.md +19 -9
- package/docs/concepts/execution-model-and-durability.md +14 -14
- package/docs/concepts/security-model.md +32 -8
- package/docs/concepts/sessions-runs-and-streaming.md +11 -9
- package/docs/connections.mdx +49 -29
- package/docs/evals/assertions.mdx +30 -29
- package/docs/evals/cases.mdx +10 -10
- package/docs/evals/judge.mdx +14 -14
- package/docs/evals/overview.mdx +15 -17
- package/docs/evals/reporters.mdx +19 -7
- package/docs/evals/running.mdx +2 -2
- package/docs/evals/targets.mdx +15 -4
- package/docs/getting-started.mdx +70 -31
- package/docs/guides/auth-and-route-protection.md +18 -12
- package/docs/guides/client/continuations.mdx +5 -13
- package/docs/guides/client/messages.mdx +2 -2
- package/docs/guides/client/output-schema.mdx +2 -2
- package/docs/guides/client/overview.mdx +4 -4
- package/docs/guides/client/streaming.mdx +6 -5
- package/docs/guides/deployment.md +22 -20
- package/docs/guides/dev-tui.md +85 -13
- package/docs/guides/dynamic-capabilities.md +24 -8
- package/docs/guides/dynamic-workflows.md +33 -12
- package/docs/guides/frontend/nextjs.mdx +28 -14
- package/docs/guides/frontend/nuxt.mdx +21 -4
- package/docs/guides/frontend/overview.mdx +11 -11
- package/docs/guides/frontend/sveltekit.mdx +20 -3
- package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
- package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
- package/docs/guides/hooks.md +14 -60
- package/docs/guides/instrumentation.md +24 -18
- package/docs/guides/remote-agents.md +19 -15
- package/docs/guides/session-context.md +12 -12
- package/docs/guides/state.md +25 -9
- package/docs/instructions.mdx +18 -10
- package/docs/introduction.md +12 -12
- package/docs/legal.md +16 -0
- package/docs/meta.json +1 -0
- package/docs/reference/cli.md +101 -53
- package/docs/reference/meta.json +1 -1
- package/docs/reference/project-layout.md +39 -32
- package/docs/reference/typescript-api.md +1 -1
- package/docs/sandbox.mdx +25 -19
- package/docs/schedules.mdx +16 -14
- package/docs/skills.mdx +8 -6
- package/docs/subagents.mdx +39 -13
- package/docs/tools.mdx +13 -9
- package/docs/tutorial/connect-a-warehouse.mdx +16 -7
- package/docs/tutorial/first-agent.mdx +21 -11
- package/docs/tutorial/guard-the-spend.mdx +11 -9
- package/docs/tutorial/how-it-runs.mdx +10 -8
- package/docs/tutorial/query-sample-data.mdx +8 -8
- package/docs/tutorial/remember-definitions.mdx +5 -5
- package/docs/tutorial/run-analysis.mdx +13 -11
- package/docs/tutorial/ship-it.mdx +70 -14
- package/docs/tutorial/team-playbooks.mdx +33 -10
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Let each user connect their own warehouse over an OAuth MCP via Vercel Connect."
|
|
2
|
+
title: "Connect a Warehouse"
|
|
3
|
+
description: "Part 4 of the Build an Agent tutorial. Let each user connect their own warehouse over an OAuth MCP via Vercel Connect."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The sample dataset got
|
|
6
|
+
The sample dataset got the analytics assistant running, but it's a stand-in. Now point the agent at a real warehouse and let each user connect their own by signing in through their browser. That's what a connection is for. It's an MCP server the model reaches through tools, with auth that Eve drives for you.
|
|
7
|
+
|
|
8
|
+
This step depends on Vercel Connect, which is in private beta. No Connect access? Keep the Step 3 sample dataset and read this step for the connection model. Steps 5 through 9 work against the sample dataset, so you can complete the tutorial without a warehouse.
|
|
7
9
|
|
|
8
10
|
The filename sets the runtime name. Put the file at `agent/connections/warehouse.ts` and it registers as `"warehouse"`, with its tools surfaced as `connection__warehouse__<tool>`.
|
|
9
11
|
|
|
@@ -24,7 +26,14 @@ export default defineMcpClientConnection({
|
|
|
24
26
|
|
|
25
27
|
`"warehouse"` is the UID you chose when registering the Connect client. By default this OAuth is user-scoped. Each end-user authorizes in their own browser, and Eve resolves that user's token before every tool call.
|
|
26
28
|
|
|
27
|
-
Connect is
|
|
29
|
+
Once Connect is enabled on your account, wire it up:
|
|
30
|
+
|
|
31
|
+
1. Install the package: `npm install @vercel/connect`.
|
|
32
|
+
2. Create the Connect client: `vercel connect create <type> --name warehouse`.
|
|
33
|
+
3. Link the client to your project.
|
|
34
|
+
4. Run `vercel link` and `vercel env pull` so `VERCEL_OIDC_TOKEN` is available locally.
|
|
35
|
+
|
|
36
|
+
For the full reference, see [Connections](../connections).
|
|
28
37
|
|
|
29
38
|
## What the user sees
|
|
30
39
|
|
|
@@ -34,7 +43,7 @@ Ask a question that needs the warehouse:
|
|
|
34
43
|
How many enterprise customers signed up last month?
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
The first time
|
|
46
|
+
The first time, the model picks a warehouse tool but there's no token yet, so the turn parks and the channel shows a "Sign in" affordance. You authorize in the browser, and once the OAuth callback completes, the turn resumes from exactly that step (the durable parking from [Step 2](./how-it-runs)) and the query runs. Later calls in the session reuse the cached per-user token, so there's no prompt.
|
|
38
47
|
|
|
39
48
|
## The token never reaches the model
|
|
40
49
|
|
|
@@ -42,6 +51,6 @@ Right before each request to the MCP server, Eve resolves the bearer and sends i
|
|
|
42
51
|
|
|
43
52
|
If you want more control, gate the connection behind approval (`approval: once()`) or narrow which tools the model sees (`tools.allow`). See [Connections](../connections).
|
|
44
53
|
|
|
45
|
-
→ Next: [
|
|
54
|
+
→ Next: [Run analysis](./run-analysis)
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
Learn more: [Connections](../connections) · [Auth and route protection](../guides/auth-and-route-protection)
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Scaffold the analytics assistant, give it an analyst persona, run it, and ask a question."
|
|
2
|
+
title: "Your First Agent"
|
|
3
|
+
description: "Part 1 of the Build an Agent tutorial. Scaffold the analytics assistant, give it an analyst persona, run it, and ask a question."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The Build an Agent tutorial constructs one app end to end, a data analytics assistant. You ask in natural language, and over the next nine steps it learns to query a warehouse, run analysis in a sandbox, remember your team's metric definitions, and refuse to exceed your query budget without asking.
|
|
7
7
|
|
|
8
8
|
Step 1 gets it talking. The scaffold bundles a small sample dataset, so your first question works with zero setup.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- Node 24 or newer and npm.
|
|
13
|
+
- A model credential. The scaffold's default model goes through the [Vercel AI Gateway](../getting-started), so you need `AI_GATEWAY_API_KEY` (or `VERCEL_OIDC_TOKEN` pulled via `vercel link`). A direct provider id like `anthropic/claude-opus-4.8` instead needs that provider's key, here `ANTHROPIC_API_KEY`.
|
|
14
|
+
|
|
15
|
+
If you have not run Eve before, complete [Getting Started](../getting-started) first. Without a credential, "Run the agent" below fails when the runtime tries to reach the model; the dev TUI's `/model` flow walks you through pasting a key or linking a project.
|
|
16
|
+
|
|
17
|
+
## Scaffold the agent
|
|
11
18
|
|
|
12
19
|
```bash
|
|
13
20
|
npx eve@latest init analytics-assistant
|
|
21
|
+
cd analytics-assistant
|
|
14
22
|
```
|
|
15
23
|
|
|
16
24
|
The command writes the starter agent with Eve's default model and built-in HTTP API
|
|
17
25
|
channel (`agent/channels/eve.ts`), installs dependencies, initializes Git, and
|
|
18
26
|
starts the development server. Stop the server before continuing with the edits
|
|
19
|
-
below. It does not create a Vercel project or deploy.
|
|
27
|
+
below. It does not create a Vercel project or deploy. `init` creates the
|
|
28
|
+
`analytics-assistant/` directory, so `cd` into it before running further
|
|
29
|
+
commands.
|
|
20
30
|
|
|
21
31
|
## Set the model
|
|
22
32
|
|
|
@@ -45,20 +55,20 @@ You are a senior data analyst. You answer questions about the team's data.
|
|
|
45
55
|
|
|
46
56
|
Instructions are identity and standing rules. On-demand procedures belong in skills (Step 7), and actions belong in tools (Step 3). See [Instructions](../instructions).
|
|
47
57
|
|
|
48
|
-
## Run
|
|
58
|
+
## Run the agent
|
|
49
59
|
|
|
50
60
|
```bash
|
|
51
|
-
|
|
61
|
+
npm run dev
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
The local runtime boots and the dev TUI opens. Ask it something it can answer from general knowledge first:
|
|
64
|
+
The `init` scaffold writes a `dev` script that runs the `eve dev` binary from the project's `node_modules`. The local runtime boots and the dev TUI opens. Ask it something it can answer from general knowledge first:
|
|
55
65
|
|
|
56
66
|
```text
|
|
57
67
|
What's a good way to measure week-over-week retention?
|
|
58
68
|
```
|
|
59
69
|
|
|
60
|
-
You
|
|
70
|
+
You get a reply that follows the analyst persona. It can't see your data yet (that comes in Step 3). First, a look at what happened under the hood.
|
|
61
71
|
|
|
62
|
-
→ Next: [
|
|
72
|
+
→ Next: [How it runs](./how-it-runs)
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
Learn more: [Getting Started](../getting-started) · [Instructions](../instructions)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Gate expensive queries with cost-based approval
|
|
2
|
+
title: "Guard the Spend"
|
|
3
|
+
description: "Part 8 of the Build an Agent tutorial. Gate expensive queries with cost-based approval. The agent pauses, asks, and resumes."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
A single warehouse query can scan terabytes and
|
|
6
|
+
A single warehouse query can scan terabytes and run up the bill. So before the analytics assistant fires off an expensive scan, make it stop and check with you. The agent pauses, asks you, and resumes with your answer. That's human-in-the-loop, and you wire it up with one field on the tool.
|
|
7
7
|
|
|
8
8
|
`needsApproval` runs before `execute`. Return `true` and the turn parks on an approval request; you answer, and the run picks up from that exact step. The function gets the tool input, so you can make the decision cost-based.
|
|
9
9
|
|
|
10
10
|
## Estimate, then gate
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This step keeps `run_sql` on the Step 3 sample dataset so you can demo the gate locally. With a real warehouse you'd gate the warehouse connection tool from Step 4 the same way, on a dry-run byte estimate instead of the toy heuristic below.
|
|
13
|
+
|
|
14
|
+
Add a cheap estimator and gate `run_sql` on it:
|
|
13
15
|
|
|
14
16
|
```ts title="agent/lib/cost.ts"
|
|
15
17
|
// Illustrative: a real warehouse exposes a dry-run byte estimate.
|
|
@@ -27,7 +29,7 @@ import { estimateScanGb } from "../lib/cost.js";
|
|
|
27
29
|
const THRESHOLD_GB = 50;
|
|
28
30
|
|
|
29
31
|
export default defineTool({
|
|
30
|
-
description: "Run a read-only SQL query against the
|
|
32
|
+
description: "Run a read-only SQL query against the analytics tables.",
|
|
31
33
|
inputSchema: z.object({ sql: z.string() }),
|
|
32
34
|
// Cost-based gate: only the expensive queries need a human yes.
|
|
33
35
|
needsApproval: ({ toolInput }) => estimateScanGb(toolInput?.sql ?? "") > THRESHOLD_GB,
|
|
@@ -48,12 +50,12 @@ Ask for something that forces a large unfiltered scan:
|
|
|
48
50
|
Total revenue across all customers, all time, broken out by day.
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
The model proposes the query, `needsApproval` returns `true`, and the turn parks. The stream emits `input.requested`, then `session.waiting`. How the prompt looks depends on the channel
|
|
53
|
+
The model proposes the query, `needsApproval` returns `true`, and the turn parks. The stream emits `input.requested`, then `session.waiting`. How the prompt looks depends on the channel, whether buttons in the TUI, Block Kit in Slack, or a UI control on the web. Approve it and the run resumes from exactly that step, then the query runs. Deny it and the tool is skipped, with the model told why.
|
|
52
54
|
|
|
53
55
|
Each session has exactly one active continuation. Answer an approval against a stale handle and it's rejected, so there's no way to double-resume the same parked turn.
|
|
54
56
|
|
|
55
|
-
The same machinery backs the built-in `ask_question` tool, where the model asks you mid-turn, and per-connection approval via `approval: once()`. See [Tools
|
|
57
|
+
The same machinery backs the built-in `ask_question` tool, where the model asks you mid-turn, and per-connection approval via `approval: once()`. See [Tools and human-in-the-loop](../tools).
|
|
56
58
|
|
|
57
|
-
→ Next: [
|
|
59
|
+
→ Next: [Ship it](./ship-it)
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
Learn more: [Tools and human-in-the-loop](../tools)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Session, turn, and durable steps
|
|
2
|
+
title: "How It Runs"
|
|
3
|
+
description: "Part 2 of the Build an Agent tutorial. Session, turn, and durable steps, and why a turn survives a crash."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The analytics assistant sent one message and got one answer. Three terms describe the model behind that.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
**
|
|
8
|
+
| Term | Meaning |
|
|
9
|
+
| ----------- | ------------------------------------------------- |
|
|
10
|
+
| **session** | Your whole conversation (durable, can span days). |
|
|
11
|
+
| **turn** | One message you send and the work it triggers. |
|
|
12
|
+
| **step** | A durable checkpoint within the turn. |
|
|
11
13
|
|
|
12
|
-
Each turn runs as a durable workflow, and Eve saves progress at every step.
|
|
14
|
+
Each turn runs as a durable workflow, and Eve saves progress at every step. Completed steps never re-run; Eve replays the recorded result. A step interrupted mid-execution re-runs, so make non-idempotent side effects like charges or emails idempotent, or gate them with approval. A turn that's waiting on you (an approval, a question) resumes whenever you answer, even if that's much later.
|
|
13
15
|
|
|
14
16
|
That's why the features in the rest of this tutorial work the way they do:
|
|
15
17
|
|
|
@@ -17,7 +19,7 @@ That's why the features in the rest of this tutorial work the way they do:
|
|
|
17
19
|
- The metric glossary in Step 6 survives across turns. State is checkpointed at step boundaries, so it sticks.
|
|
18
20
|
- The spend approval in Step 8 pauses the turn on your yes/no, then picks up exactly where it left off.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
You author capabilities, including tools, instructions, channels, and skills. Eve drives the model-to-tool loop and decides when a turn continues, waits, or ends. You never write that loop yourself.
|
|
21
23
|
|
|
22
24
|
→ Next: [Step 3: Query sample data](./query-sample-data)
|
|
23
25
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Add a run_sql tool over the bundled sample dataset and watch the tool loop."
|
|
2
|
+
title: "Query Sample Data"
|
|
3
|
+
description: "Part 3 of the Build an Agent tutorial. Add a run_sql tool over the bundled sample dataset and watch the tool loop."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The analytics assistant can hold a conversation, but it can't see a single row of data. Give it a tool. A tool is the action primitive. Typed input goes in, your code runs, structured output comes back. The name the model sees is the filename, so `agent/tools/run_sql.ts` becomes the tool `run_sql`.
|
|
7
7
|
|
|
8
8
|
## A tiny sample dataset
|
|
9
9
|
|
|
10
|
-
To make the first query work without
|
|
10
|
+
To make the first query work without setup, bundle a small in-memory dataset under `agent/lib/`. Keep it tiny. This is throwaway scaffolding, not the real warehouse (that comes in Step 4).
|
|
11
11
|
|
|
12
12
|
```ts title="agent/lib/sample-db.ts"
|
|
13
13
|
// A toy SQLite-in-memory stand-in. Swap for your real warehouse in Step 4.
|
|
@@ -42,7 +42,7 @@ export async function runReadOnlySql(sql: string) {
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Define the run_sql tool
|
|
46
46
|
|
|
47
47
|
```ts title="agent/tools/run_sql.ts"
|
|
48
48
|
import { defineTool } from "eve/tools";
|
|
@@ -68,7 +68,7 @@ Tools run in your app runtime with full `process.env`, not in the sandbox. The `
|
|
|
68
68
|
|
|
69
69
|
## Watch the tool loop
|
|
70
70
|
|
|
71
|
-
Restart `
|
|
71
|
+
Restart the dev server with `npm run dev` and ask:
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
74
|
Which customer has spent the most, and how much?
|
|
@@ -76,6 +76,6 @@ Which customer has spent the most, and how much?
|
|
|
76
76
|
|
|
77
77
|
Watch the loop play out in the TUI. The model emits a `run_sql` call, Eve runs your `execute`, and the rows come back as a tool result. The model reads them and answers with a real number. Eve drove the whole loop. All you supplied was the tool.
|
|
78
78
|
|
|
79
|
-
→ Next: [
|
|
79
|
+
→ Next: [Connect a warehouse](./connect-a-warehouse)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Learn more: [Tools](../tools)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Use defineState to remember the team's metric glossary across turns."
|
|
2
|
+
title: "Remember Definitions"
|
|
3
|
+
description: "Part 6 of the Build an Agent tutorial. Use defineState to remember the team's metric glossary across turns."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Every team has house definitions.
|
|
6
|
+
Every team has house definitions for the analytics assistant. "Active" means a purchase in the last 30 days, revenue is net of refunds, a "week" starts Monday. Re-explaining all of that on every turn is a waste. State gives the agent a place to keep them.
|
|
7
7
|
|
|
8
8
|
`defineState(name, initial)` creates a typed, named slot that survives across step and turn boundaries within a session. You read it with `get()` and change it with `update()`.
|
|
9
9
|
|
|
@@ -67,6 +67,6 @@ The second turn is a separate turn in the same session, yet the definition is st
|
|
|
67
67
|
|
|
68
68
|
State is scoped to a session and isolated per agent, so a subagent starts with fresh state and never sees the parent's. Need to reset something each turn? Call `update(() => fresh)` in a lifecycle hook. More in [State](../guides/state).
|
|
69
69
|
|
|
70
|
-
→ Next: [
|
|
70
|
+
→ Next: [Team playbooks](./team-playbooks)
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Learn more: [State](../guides/state)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Seed the warehouse schema into the sandbox workspace, then compute and chart beyond SQL."
|
|
2
|
+
title: "Run Analysis"
|
|
3
|
+
description: "Part 5 of the Build an Agent tutorial. Seed the warehouse schema into the sandbox workspace, then compute and chart beyond SQL."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
SQL tells
|
|
6
|
+
SQL tells the analytics assistant the numbers, but a cohort curve, a forecast, or a chart needs real computation. That's what the sandbox is for. It's an isolated bash environment with a `/workspace` filesystem, and every agent gets exactly one.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This takes two pieces. First seed reference files the model can read, then compute against them.
|
|
9
9
|
|
|
10
10
|
## Seed the schema into the workspace
|
|
11
11
|
|
|
12
|
-
Mount the warehouse schema into the sandbox so the model isn't guessing at table shapes. Seeding uses the folder sandbox layout
|
|
12
|
+
Mount the warehouse schema into the sandbox so the model isn't guessing at table shapes. Seeding uses the folder sandbox layout, where anything under `agent/sandbox/workspace/` lands in the live `/workspace` cwd at session bootstrap.
|
|
13
13
|
|
|
14
14
|
```text
|
|
15
15
|
agent/sandbox/
|
|
@@ -25,7 +25,7 @@ CREATE TABLE orders (id INT, customer_id INT, amount_cents INT, created_at D
|
|
|
25
25
|
CREATE TABLE customers (id INT, name TEXT, plan TEXT, signed_up_at DATE);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Top-level workspace entries get advertised to the model automatically, so it knows `schema.sql` is there to read. No `agent/sandbox/sandbox.ts` required. A `workspace/` folder keeps the default sandbox and
|
|
28
|
+
Top-level workspace entries get advertised to the model automatically, so it knows `schema.sql` is there to read. No `agent/sandbox/sandbox.ts` required. A `workspace/` folder keeps the default sandbox and seeds your files into it.
|
|
29
29
|
|
|
30
30
|
## Compute and chart in the sandbox
|
|
31
31
|
|
|
@@ -67,13 +67,15 @@ export default defineTool({
|
|
|
67
67
|
});
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
This tool shells out to `python` with matplotlib, which the sandbox base image does not preinstall. Install the runtime in sandbox bootstrap (or bake it into a custom image) so `python plot.py` resolves. See [Sandbox](../sandbox) for where bootstrap runs.
|
|
71
|
+
|
|
72
|
+
Now ask for something past plain SQL. If you skipped Step 4, this still works against the Step 3 sample dataset:
|
|
71
73
|
|
|
72
74
|
```text
|
|
73
|
-
Plot
|
|
75
|
+
Plot total order revenue per customer.
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
The model queries the warehouse
|
|
78
|
+
The model queries for the numbers (the warehouse from Step 4, or the sample dataset if you skipped it), checks `schema.sql` to get the grain right, then calls `chart_series` to render the PNG in `/workspace`.
|
|
77
79
|
|
|
78
80
|
## Secrets stay out of the sandbox
|
|
79
81
|
|
|
@@ -81,6 +83,6 @@ The sandbox has no `process.env` and no access to your app's secrets. Your wareh
|
|
|
81
83
|
|
|
82
84
|
The local backend runs the sandbox on your laptop during `eve dev`; on Vercel it runs on Vercel Sandbox. Lifecycle, backends, and network policy are in [Sandbox](../sandbox).
|
|
83
85
|
|
|
84
|
-
→ Next: [
|
|
86
|
+
→ Next: [Remember definitions](./remember-definitions)
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
Learn more: [Sandbox](../sandbox)
|
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Put a web dashboard on the agent with useEveAgent, replace placeholderAuth, and deploy to Vercel."
|
|
2
|
+
title: "Ship It"
|
|
3
|
+
description: "Part 9 of the Build an Agent tutorial. Put a web dashboard on the agent with useEveAgent, replace placeholderAuth, and deploy to Vercel."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The assistant runs fine in the TUI. Now ship it for real
|
|
6
|
+
The analytics assistant runs fine in the TUI. Now ship it for real, as a web dashboard your team logs into, behind actual auth, deployed on Vercel. There are three pieces to wire up. A React UI, the channel's auth, and the deploy itself.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Add the Web Chat app
|
|
9
|
+
|
|
10
|
+
Step 1 scaffolded the agent without a web frontend. Add one now with `eve channels add`, run from the `analytics-assistant/` directory:
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
```bash
|
|
13
|
+
npx eve channels add web
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This adds a Next.js app (`next.config.ts`, `app/page.tsx`, `app/_components/`) wired to the existing Eve channel, plus the chat UI components and their dependencies. Run `npm install` afterward to install the added packages. The generated `next.config.ts` wraps your config with `withEve`, which wires the Eve routes automatically:
|
|
11
17
|
|
|
12
18
|
```ts title="next.config.ts"
|
|
13
19
|
import type { NextConfig } from "next";
|
|
14
20
|
import { withEve } from "eve/next";
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
const nextConfig: NextConfig = {};
|
|
23
|
+
|
|
24
|
+
export default withEve(nextConfig);
|
|
17
25
|
```
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
## A dashboard with `useEveAgent`
|
|
28
|
+
|
|
29
|
+
The dashboard talks to the built-in Eve HTTP channel (`agent/channels/eve.ts`). On the browser side, `useEveAgent` handles session creation, streaming, and HITL. The scaffold renders its chat from `app/_components/agent-chat.tsx`, mounted by `app/page.tsx`. That component is fuller than you need to start, so replace its contents with this minimal version:
|
|
30
|
+
|
|
31
|
+
```tsx title="app/_components/agent-chat.tsx"
|
|
20
32
|
"use client";
|
|
21
33
|
|
|
22
34
|
import { useEveAgent } from "eve/react";
|
|
23
35
|
|
|
24
|
-
export function
|
|
36
|
+
export function AgentChat() {
|
|
25
37
|
const agent = useEveAgent();
|
|
26
38
|
const isBusy = agent.status === "submitted" || agent.status === "streaming";
|
|
27
39
|
|
|
@@ -51,11 +63,37 @@ export function AnalyticsChat() {
|
|
|
51
63
|
}
|
|
52
64
|
```
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
The generated `app/page.tsx` already imports and renders this `AgentChat` export, so no other wiring is needed:
|
|
67
|
+
|
|
68
|
+
```tsx title="app/page.tsx"
|
|
69
|
+
import { AgentChat } from "@/app/_components/agent-chat";
|
|
70
|
+
|
|
71
|
+
export default function Page() {
|
|
72
|
+
return <AgentChat />;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`agent.data.messages` and `agent.status` cover most chat UIs. The hook also surfaces HITL prompts (the spend approval from [Step 8](./guard-the-spend)), so the dashboard can render approve/deny controls. For the full API, see [Frontend](../guides/frontend/overview).
|
|
55
77
|
|
|
56
78
|
## Replace `placeholderAuth`
|
|
57
79
|
|
|
58
|
-
The scaffold's channel ships with `placeholderAuth()`, which fails closed
|
|
80
|
+
The scaffold's channel ships with `placeholderAuth()`, which fails closed. It rejects production traffic so an unauthenticated app can't go live by accident. Swap it for your app's real auth before you deploy.
|
|
81
|
+
|
|
82
|
+
Your auth lives in one module that turns a request into a user. Create `agent/lib/auth.ts` and wire your real provider (a cookie session, Auth.js, Clerk) in here. The stub below returns a fixed user so the page compiles and runs end to end:
|
|
83
|
+
|
|
84
|
+
```ts title="agent/lib/auth.ts"
|
|
85
|
+
export interface AppUser {
|
|
86
|
+
id: string;
|
|
87
|
+
team: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Replace with your real session/provider lookup.
|
|
91
|
+
export async function authenticate(_request: Request): Promise<AppUser | null> {
|
|
92
|
+
return { id: "demo-user", team: "growth" };
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Now point the channel at it. Replace the contents of `agent/channels/eve.ts`, which Step 7 left with a dev-only `devTeam` entry and `placeholderAuth()`. List your app auth first, ahead of the catch-all helpers, so any entry that doesn't recognize the caller falls through to the next one:
|
|
59
97
|
|
|
60
98
|
```ts title="agent/channels/eve.ts"
|
|
61
99
|
import { eveChannel } from "eve/channels/eve";
|
|
@@ -81,7 +119,7 @@ export default eveChannel({
|
|
|
81
119
|
|
|
82
120
|
That `team` attribute is exactly what the dynamic playbook in [Step 7](./team-playbooks) reads from `ctx.session.auth`. Identity is set in this one place and flows out to every capability from there.
|
|
83
121
|
|
|
84
|
-
## Deploy
|
|
122
|
+
## Deploy to Vercel
|
|
85
123
|
|
|
86
124
|
```bash
|
|
87
125
|
vercel deploy
|
|
@@ -90,11 +128,29 @@ vercel deploy
|
|
|
90
128
|
On Vercel, the web app stays public and the Eve runtime sits behind it on the same origin, with the sandbox running on Vercel Sandbox. You can smoke-test the deployment without leaving the CLI:
|
|
91
129
|
|
|
92
130
|
```bash
|
|
93
|
-
eve dev https://your-analytics-app.vercel.app
|
|
131
|
+
npx eve dev https://your-analytics-app.vercel.app
|
|
94
132
|
```
|
|
95
133
|
|
|
96
134
|
That's the full assistant, deployed and authed. It queries the warehouse, runs analysis in a sandbox, charts the results, remembers your team's definitions, loads the right playbook per team, and asks before it spends.
|
|
97
135
|
|
|
98
|
-
|
|
136
|
+
## What you learned
|
|
137
|
+
|
|
138
|
+
Across the nine steps you built and shipped one agent, and along the way you used:
|
|
139
|
+
|
|
140
|
+
- **Tools** to give the model typed actions (`run_sql`, `chart_series`, `define_metric`).
|
|
141
|
+
- **Connections** to reach a warehouse over an OAuth MCP, with per-user tokens Eve resolves for you.
|
|
142
|
+
- **The sandbox** to compute and chart beyond SQL in an isolated `/workspace`.
|
|
143
|
+
- **State** (`defineState`) to remember the team's glossary across turns.
|
|
144
|
+
- **Dynamic skills** (`defineDynamic`) to load the right team playbook per caller.
|
|
145
|
+
- **Human-in-the-loop** approval (`needsApproval`) to gate expensive queries.
|
|
146
|
+
- **Channel auth** to turn a request into an authenticated principal.
|
|
147
|
+
- **Deployment** to Vercel, with the runtime behind your web app.
|
|
148
|
+
|
|
149
|
+
## Next steps
|
|
150
|
+
|
|
151
|
+
- [Connections](../connections) for tool allowlists and per-connection approval.
|
|
152
|
+
- [Sandbox](../sandbox) for backends, lifecycle, and network policy.
|
|
153
|
+
- [Dynamic capabilities](../guides/dynamic-capabilities) for schema-derived dynamic tools, a read-only analyst subagent, and model-authored report workflows on this same example.
|
|
154
|
+
- [Auth and route protection](../guides/auth-and-route-protection) for production auth patterns.
|
|
99
155
|
|
|
100
|
-
|
|
156
|
+
Learn more: [Frontend](../guides/frontend/overview) · [Auth and route protection](../guides/auth-and-route-protection) · [Deployment](../guides/deployment)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "Load the caller's team playbook with a dynamic skill keyed on the principal."
|
|
2
|
+
title: "Team Playbooks"
|
|
3
|
+
description: "Part 7 of the Build an Agent tutorial. Load the caller's team playbook with a dynamic skill keyed on the principal."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The glossary from [Step 6](./remember-definitions) is per-session. But your teams have standing analysis conventions (Growth runs cohort retention a particular way, Finance has its own revenue-recognition rules), and those shouldn't bleed across tenants. Load the right team's playbook for whoever is asking.
|
|
6
|
+
The glossary from [Step 6](./remember-definitions) is per-session. But your teams have standing analysis conventions for the analytics assistant (Growth runs cohort retention a particular way, Finance has its own revenue-recognition rules), and those shouldn't bleed across tenants. Load the right team's playbook for whoever is asking.
|
|
7
7
|
|
|
8
|
-
A skill is an on-demand procedure
|
|
8
|
+
A skill is an on-demand procedure. The model pulls it in with `load_skill` only when a turn needs it. Make it dynamic and the skill gets decided at runtime instead of baked in. A `defineDynamic` resolver reads the session and returns a `defineSkill` (or nothing). Here you key that decision on the caller's identity in `ctx.session.auth`.
|
|
9
9
|
|
|
10
10
|
## A playbook per principal
|
|
11
11
|
|
|
12
|
-
`ctx.session.auth.current` holds the most recent caller, or `null` if there isn't one. Its `attributes` are the claims your auth layer stamped on, team
|
|
12
|
+
`ctx.session.auth.current` holds the most recent caller, or `null` if there isn't one. Its `attributes` are the claims your auth layer stamped on, including the team. Read the team, look up that team's playbook, and emit a skill for it:
|
|
13
13
|
|
|
14
14
|
```ts title="agent/skills/team-playbook.ts"
|
|
15
15
|
import { defineDynamic, defineSkill } from "eve/skills";
|
|
@@ -48,16 +48,39 @@ export default defineDynamic({
|
|
|
48
48
|
});
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`session.started` fires once per session. The resolver reads the team
|
|
51
|
+
`session.started` fires once per session. The resolver reads the team once, and the resulting skill stays available for every turn that follows. Returning `null` produces no skill, so a caller with no team gets no playbook.
|
|
52
52
|
|
|
53
53
|
## See it route
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
The team comes from authenticated claims, which the auth layer stamps on in [Step 9](./ship-it). Until then `ctx.session.auth.current` has no `team`, so the resolver returns `null` and no playbook loads. To verify routing now, stamp a team in local dev. Add a dev-only entry to `agent/channels/eve.ts` ahead of `localDev()`, and remove it before Step 9 wires real auth:
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
```ts title="agent/channels/eve.ts"
|
|
58
|
+
import { eveChannel } from "eve/channels/eve";
|
|
59
|
+
import { localDev, placeholderAuth, vercelOidc, type AuthFn } from "eve/channels/auth";
|
|
60
|
+
|
|
61
|
+
// Dev-only: stamp a team so Step 7's playbook resolver has something to read.
|
|
62
|
+
// Remove before Step 9.
|
|
63
|
+
const devTeam: AuthFn<Request> = () =>
|
|
64
|
+
process.env.NODE_ENV === "production"
|
|
65
|
+
? null
|
|
66
|
+
: {
|
|
67
|
+
attributes: { team: "growth" },
|
|
68
|
+
authenticator: "dev-team",
|
|
69
|
+
principalId: "dev",
|
|
70
|
+
principalType: "user",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default eveChannel({
|
|
74
|
+
auth: [devTeam, localDev(), vercelOidc(), placeholderAuth()],
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Restart with `npm run dev` and ask "what's our 8-week retention?" The model sees the Growth playbook fits, calls `load_skill`, and applies the Growth conventions to that turn (weekly cohorts, no trial accounts). Switch `team` to `"finance"`, restart, and the same question routes to Finance's playbook instead.
|
|
79
|
+
|
|
80
|
+
Because the team comes from authenticated claims, not from the message, one tenant can't borrow another's playbook through the message content.
|
|
58
81
|
|
|
59
82
|
The same `defineDynamic` resolver drives dynamic tools and instructions too. For the full mechanism, see [Dynamic capabilities](../guides/dynamic-capabilities).
|
|
60
83
|
|
|
61
|
-
→ Next: [
|
|
84
|
+
→ Next: [Guard the spend](./guard-the-spend)
|
|
62
85
|
|
|
63
|
-
|
|
86
|
+
Learn more: [Skills](../skills) · [Dynamic capabilities](../guides/dynamic-capabilities)
|