eve 0.9.8 → 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.
Files changed (91) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +14 -0
  3. package/dist/src/cli/banner.d.ts +6 -3
  4. package/dist/src/cli/banner.js +1 -1
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/dev/tui/agent-header.js +1 -1
  7. package/dist/src/cli/run.js +1 -1
  8. package/dist/src/evals/runner/run-evals.d.ts +1 -1
  9. package/dist/src/harness/input-requests.d.ts +20 -1
  10. package/dist/src/harness/input-requests.js +1 -1
  11. package/dist/src/harness/tool-loop.js +1 -1
  12. package/dist/src/internal/application/package.js +1 -1
  13. package/dist/src/protocol/message.d.ts +11 -2
  14. package/dist/src/protocol/message.js +2 -2
  15. package/dist/src/public/next/index.d.ts +1 -7
  16. package/dist/src/public/next/index.js +1 -1
  17. package/dist/src/public/next/vercel-output-config.js +1 -1
  18. package/dist/src/setup/scaffold/create/project.js +1 -1
  19. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  20. package/dist/src/setup/scaffold/create/web-template.js +27 -4
  21. package/dist/src/setup/scaffold/update/channels.js +1 -1
  22. package/docs/README.md +19 -5
  23. package/docs/agent-config.md +16 -19
  24. package/docs/channels/custom.mdx +24 -18
  25. package/docs/channels/discord.mdx +25 -13
  26. package/docs/channels/eve.mdx +24 -6
  27. package/docs/channels/github.mdx +41 -10
  28. package/docs/channels/linear.mdx +29 -36
  29. package/docs/channels/overview.mdx +16 -4
  30. package/docs/channels/slack.mdx +59 -32
  31. package/docs/channels/teams.mdx +19 -7
  32. package/docs/channels/telegram.mdx +23 -9
  33. package/docs/channels/twilio.mdx +28 -6
  34. package/docs/concepts/context-control.md +24 -41
  35. package/docs/concepts/default-harness.md +19 -9
  36. package/docs/concepts/execution-model-and-durability.md +14 -14
  37. package/docs/concepts/security-model.md +32 -8
  38. package/docs/concepts/sessions-runs-and-streaming.md +11 -9
  39. package/docs/connections.mdx +49 -29
  40. package/docs/evals/assertions.mdx +30 -29
  41. package/docs/evals/cases.mdx +10 -10
  42. package/docs/evals/judge.mdx +14 -14
  43. package/docs/evals/overview.mdx +15 -17
  44. package/docs/evals/reporters.mdx +19 -7
  45. package/docs/evals/running.mdx +2 -2
  46. package/docs/evals/targets.mdx +15 -4
  47. package/docs/getting-started.mdx +70 -31
  48. package/docs/guides/auth-and-route-protection.md +18 -12
  49. package/docs/guides/client/continuations.mdx +5 -13
  50. package/docs/guides/client/messages.mdx +2 -2
  51. package/docs/guides/client/output-schema.mdx +2 -2
  52. package/docs/guides/client/overview.mdx +4 -4
  53. package/docs/guides/client/streaming.mdx +6 -5
  54. package/docs/guides/deployment.md +22 -20
  55. package/docs/guides/dev-tui.md +85 -13
  56. package/docs/guides/dynamic-capabilities.md +24 -8
  57. package/docs/guides/dynamic-workflows.md +33 -12
  58. package/docs/guides/frontend/nextjs.mdx +28 -14
  59. package/docs/guides/frontend/nuxt.mdx +21 -4
  60. package/docs/guides/frontend/overview.mdx +11 -11
  61. package/docs/guides/frontend/sveltekit.mdx +20 -3
  62. package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
  63. package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
  64. package/docs/guides/hooks.md +14 -60
  65. package/docs/guides/instrumentation.md +24 -18
  66. package/docs/guides/remote-agents.md +19 -15
  67. package/docs/guides/session-context.md +12 -12
  68. package/docs/guides/state.md +25 -9
  69. package/docs/instructions.mdx +18 -10
  70. package/docs/introduction.md +12 -12
  71. package/docs/legal.md +16 -0
  72. package/docs/meta.json +1 -0
  73. package/docs/reference/cli.md +101 -53
  74. package/docs/reference/meta.json +1 -1
  75. package/docs/reference/project-layout.md +39 -32
  76. package/docs/reference/typescript-api.md +1 -1
  77. package/docs/sandbox.mdx +25 -19
  78. package/docs/schedules.mdx +16 -14
  79. package/docs/skills.mdx +8 -6
  80. package/docs/subagents.mdx +39 -13
  81. package/docs/tools.mdx +13 -9
  82. package/docs/tutorial/connect-a-warehouse.mdx +16 -7
  83. package/docs/tutorial/first-agent.mdx +21 -11
  84. package/docs/tutorial/guard-the-spend.mdx +11 -9
  85. package/docs/tutorial/how-it-runs.mdx +10 -8
  86. package/docs/tutorial/query-sample-data.mdx +8 -8
  87. package/docs/tutorial/remember-definitions.mdx +5 -5
  88. package/docs/tutorial/run-analysis.mdx +13 -11
  89. package/docs/tutorial/ship-it.mdx +70 -14
  90. package/docs/tutorial/team-playbooks.mdx +33 -10
  91. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  ---
2
- title: "Step 4: Connect a Warehouse"
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 you running, but it's a stand-in. The next step is to 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: an MCP server the model reaches through tools, with auth that Eve drives for you.
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 in private beta. Once enabled: `npm install @vercel/connect`, create the Connect client (`vercel connect create <type> --name warehouse`), link it to your project, then `vercel link` and `vercel env pull` so `VERCEL_OIDC_TOKEN` is available locally. Full setup: [Connections](../connections).
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 around, 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.
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: [Step 5: Run analysis](./run-analysis)
54
+ → Next: [Run analysis](./run-analysis)
46
55
 
47
- Depth: [Connections](../connections) · [Auth & route protection](../guides/auth-and-route-protection)
56
+ Learn more: [Connections](../connections) · [Auth and route protection](../guides/auth-and-route-protection)
@@ -1,22 +1,32 @@
1
1
  ---
2
- title: "Step 1: Your First Agent"
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
- This tutorial builds 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 teams metric definitions, and refuse to torch your query budget without asking.
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
- ## Scaffold
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 it
58
+ ## Run the agent
49
59
 
50
60
  ```bash
51
- eve dev
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’ll get a reply that follows the analyst persona. It cant see your data yet (that’s Step 3). First, a look at what just happened under the hood.
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: [Step 2: How it runs](./how-it-runs)
72
+ → Next: [How it runs](./how-it-runs)
63
73
 
64
- Depth: [Getting Started](../getting-started) · [Instructions](../instructions)
74
+ Learn more: [Getting Started](../getting-started) · [Instructions](../instructions)
@@ -1,15 +1,17 @@
1
1
  ---
2
- title: "Step 8: Guard the Spend"
3
- description: "Gate expensive queries with cost-based approval: the agent pauses, asks, and resumes."
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 bill you for the privilege. So before the agent fires off an expensive scan, make it stop and check with you: for a costly query, 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.
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
- Add a cheap estimator (a real warehouse exposes a dry-run byte estimate, so keep the helper illustrative) and gate `run_sql` on it:
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 warehouse.",
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: buttons in the TUI, Block Kit in Slack, 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.
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: human-in-the-loop](../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: [Step 9: Ship it](./ship-it)
59
+ → Next: [Ship it](./ship-it)
58
60
 
59
- Depth: [Tools: human-in-the-loop](../tools)
61
+ Learn more: [Tools and human-in-the-loop](../tools)
@@ -1,15 +1,17 @@
1
1
  ---
2
- title: "Step 2: How It Runs"
3
- description: "Session, turn, and durable steps: what you just watched, and why it survives a crash."
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
- You sent one message and got one answer. Here's the model behind that, in three words.
6
+ The analytics assistant sent one message and got one answer. Three terms describe the model behind that.
7
7
 
8
- **session** your whole conversation (durable, can span days)
9
- **turn** one message you send and the work it triggers
10
- **step** a durable checkpoint within the turn
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. Restart in the middle of a turn and it picks up from the last completed step, without re-running your tool calls. A turn that's waiting on you (an approval, a question) resumes whenever you answer, even if that's much later.
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
- Your job is to author capabilities: tools, instructions, channels, skills. Eve drives the modeltool loop and decides when a turn continues, waits, or ends. You never write that loop yourself.
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: "Step 3: Query Sample Data"
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
- Right now the agent can hold a conversation but it can't see a single row of data. So 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 just the filename, so `agent/tools/run_sql.ts` becomes the tool `run_sql`.
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 any 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).
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
- ## The tool
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 `eve dev` and ask:
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: [Step 4: Connect a warehouse](./connect-a-warehouse)
79
+ → Next: [Connect a warehouse](./connect-a-warehouse)
80
80
 
81
- Depth: [Tools](../tools)
81
+ Learn more: [Tools](../tools)
@@ -1,9 +1,9 @@
1
1
  ---
2
- title: "Step 6: Remember Definitions"
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. 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 it.
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: [Step 7: Team playbooks](./team-playbooks)
70
+ → Next: [Team playbooks](./team-playbooks)
71
71
 
72
- Depth: [State](../guides/state)
72
+ Learn more: [State](../guides/state)
@@ -1,15 +1,15 @@
1
1
  ---
2
- title: "Step 5: Run Analysis"
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 you the numbers, but a cohort curve, a forecast, or a chart needs real computation. That's what the sandbox is for: an isolated bash environment with a `/workspace` filesystem. Every agent gets exactly one.
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
- There are two pieces. First seed reference files the model can read, then compute against them.
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: anything under `agent/sandbox/workspace/` lands in the live `/workspace` cwd at session bootstrap.
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 just seeds your files into it.
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
- Now ask for something past plain SQL:
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 weekly revenue for the last 8 weeks.
75
+ Plot total order revenue per customer.
74
76
  ```
75
77
 
76
- The model queries the warehouse (Step 4) for the numbers, checks `schema.sql` to get the grain right, then calls `chart_series` to render the PNG in `/workspace`.
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: [Step 6: Remember definitions](./remember-definitions)
86
+ → Next: [Remember definitions](./remember-definitions)
85
87
 
86
- Depth: [Sandbox](../sandbox)
88
+ Learn more: [Sandbox](../sandbox)
@@ -1,27 +1,39 @@
1
1
  ---
2
- title: "Step 9: Ship It"
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: a web dashboard your team logs into, behind actual auth, deployed on Vercel. Three pieces to wire up: a React UI, the channel's auth, and the deploy itself.
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
- ## A dashboard with `useEveAgent`
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
- The dashboard talks to the built-in Eve HTTP channel (`agent/channels/eve.ts`). On the browser side, `useEveAgent` takes care of session creation, streaming, and HITL. In a Next.js app, wrap the config and the routes get wired automatically:
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
- export default withEve({} satisfies NextConfig);
22
+ const nextConfig: NextConfig = {};
23
+
24
+ export default withEve(nextConfig);
17
25
  ```
18
26
 
19
- ```tsx title="app/dashboard/chat.tsx"
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 AnalyticsChat() {
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
- `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. Full API: [Frontend](../guides/frontend/overview).
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: 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. Order matters here. 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:
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
- Depth: [Frontend](../guides/frontend/overview) · [Auth & route protection](../guides/auth-and-route-protection) · [Deployment](../guides/deployment)
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
- Go deeper on this same example in [Advanced](../guides/dynamic-capabilities): schema-derived dynamic tools, a read-only analyst subagent, and model-authored report workflows.
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: "Step 7: Team Playbooks"
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: the model pulls it in with `load_skill` only when a turn actually 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 we key that decision on the caller's identity in `ctx.session.auth`.
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 included. Read the team, look up that team's playbook, and emit a skill for it:
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 a single time, and the resulting skill stays available for every turn that follows. Returning `null` produces no skill at all, so a caller with no team simply gets no playbook.
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
- A Growth user asks "what's our 8-week retention?" The model sees the playbook fits, calls `load_skill`, and applies the Growth conventions to that turn: weekly cohorts, no trial accounts. Ask the same question as a Finance user and you'll never see the Growth playbook. You get Finance's.
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
- The team comes from authenticated claims, not from the message, so one tenant can't borrow another's playbook by asking nicely. (Those `attributes` are set by the auth layer in [Step 9](./ship-it).)
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: [Step 8: Guard the spend](./guard-the-spend)
84
+ → Next: [Guard the spend](./guard-the-spend)
62
85
 
63
- Depth: [Skills](../skills) · [Dynamic capabilities](../guides/dynamic-capabilities)
86
+ Learn more: [Skills](../skills) · [Dynamic capabilities](../guides/dynamic-capabilities)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.9.8",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [