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.
- package/CHANGELOG.md +17 -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,17 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Context Control"
|
|
3
|
-
description: "Control what
|
|
3
|
+
description: "Control what an Eve agent's model sees and when, across instructions, skills, the workspace, and subagents."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Eve gives you a few levers for controlling what the model sees and when
|
|
6
|
+
Eve gives you a few levers for controlling what the model sees and when. `instructions.md` (or `instructions.ts`) is always on, `skills/` are available but loaded on demand, and the workspace and sandbox are visible through tools rather than pasted into the prompt.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `instructions.md` (or `instructions.ts`) is always on.
|
|
11
|
-
- `skills/` are available but loaded on demand.
|
|
12
|
-
- The workspace and sandbox are visible through tools, not pasted into the prompt.
|
|
13
|
-
|
|
14
|
-
## 1. Base identity with `instructions.md`
|
|
8
|
+
## Base identity with `instructions.md`
|
|
15
9
|
|
|
16
10
|
Use `instructions.md` for the core contract of the agent.
|
|
17
11
|
|
|
@@ -22,9 +16,9 @@ used a tool.
|
|
|
22
16
|
|
|
23
17
|
Keep this file focused on stable behavior that should apply on every turn.
|
|
24
18
|
|
|
25
|
-
##
|
|
19
|
+
## Compose instructions in TypeScript with `instructions.ts`
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
To build the instructions prompt from typed helpers, lib code, or environment-derived values, author it as a module instead of markdown.
|
|
28
22
|
|
|
29
23
|
```ts title="agent/instructions.ts"
|
|
30
24
|
import { defineInstructions } from "eve/instructions";
|
|
@@ -37,13 +31,9 @@ export default defineInstructions({
|
|
|
37
31
|
|
|
38
32
|
Module-backed instructions run once at build time. Eve captures the resulting markdown into the compiled manifest, so the runtime serves the same prompt every session without re-running the module.
|
|
39
33
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
Skills are not part of the always-on prompt by default.
|
|
43
|
-
|
|
44
|
-
Instead, Eve advertises the available skills and adds a framework-owned `load_skill` tool. When the request clearly matches a skill description, or the user names a skill explicitly, the model activates that skill, and Eve appends the skill's markdown to the active instructions for later turn work.
|
|
34
|
+
## Load procedures on demand with `skills/`
|
|
45
35
|
|
|
46
|
-
|
|
36
|
+
Skills stay out of the always-on prompt by default, which keeps rich procedures available without bloating every turn. Eve advertises the available skills and adds a framework-owned `load_skill` tool. When the request clearly matches a skill description, or the user names a skill explicitly, the model activates that skill, and Eve appends the skill's markdown to the active instructions for later turn work.
|
|
47
37
|
|
|
48
38
|
### Flat skill
|
|
49
39
|
|
|
@@ -62,48 +52,41 @@ When the task is novel or ambiguous, gather evidence first, then answer with the
|
|
|
62
52
|
remaining uncertainty.
|
|
63
53
|
```
|
|
64
54
|
|
|
65
|
-
Packaged skills are useful when you also want sibling files such as `references/`, `assets/`, or `scripts/` under the same skill directory. Those
|
|
55
|
+
Packaged skills are useful when you also want sibling files such as `references/`, `assets/`, or `scripts/` under the same skill directory. Those paths show up under the runtime workspace root, so the model can inspect them with the normal file or shell tools instead of pasting their content into the prompt.
|
|
66
56
|
|
|
67
57
|
See [Skills](../skills) for the full authoring model and install notes.
|
|
68
58
|
|
|
69
|
-
##
|
|
59
|
+
## Put runtime files in the workspace, not the prompt
|
|
70
60
|
|
|
71
|
-
Eve does not inline the entire authored surface into the prompt. Instead, it gives the model a shallow workspace hint and runtime tools to inspect deeper when needed.
|
|
61
|
+
Eve does not inline the entire authored surface into the prompt. Instead, it gives the model a shallow workspace hint and runtime tools to inspect deeper when needed. Skill files are available under the active workspace root, and the model inspects them with the shared `bash` tool, which keeps prompts smaller and makes file and command work explicit.
|
|
72
62
|
|
|
73
63
|
See [Sandbox](../sandbox) for the workspace and sandbox model.
|
|
74
64
|
|
|
75
|
-
##
|
|
65
|
+
## Delegate to a specialist with a subagent
|
|
76
66
|
|
|
77
|
-
If a task deserves its own prompt and tool surface, use a local subagent instead of overloading the root agent. Subagents are a context-control
|
|
67
|
+
If a task deserves its own prompt and tool surface, use a local subagent instead of overloading the root agent. Subagents are a context-control lever too. They get their own `instructions.md`, tools, and sandbox, and they run inside their own delegated context instead of extending the root agent inline.
|
|
78
68
|
|
|
79
69
|
See [Subagents](../subagents).
|
|
80
70
|
|
|
81
71
|
## Dynamic context with `defineDynamic`
|
|
82
72
|
|
|
83
|
-
The levers above are static
|
|
73
|
+
The levers above are static, authored once and the same on every session. When the right context depends on who is calling (their team, tenant, plan, or feature flags), resolve it at runtime instead. `defineDynamic` in `agent/instructions/` returns the per-session system prompt, and `defineDynamic` in `agent/skills/` returns the set of skills a caller can load. Both read `ctx.session.auth` or channel metadata, so a caller on the billing team gets the billing instructions and playbook while no one else sees them. See [Dynamic capabilities](../guides/dynamic-capabilities) for the resolver API and when each event fires.
|
|
84
74
|
|
|
85
|
-
##
|
|
75
|
+
## Recommended context layout
|
|
86
76
|
|
|
87
|
-
|
|
77
|
+
Pick the lever by what the context is for:
|
|
88
78
|
|
|
89
|
-
- `instructions.md` for the agent's permanent identity.
|
|
79
|
+
- `instructions.md` for the agent's permanent identity. Keep it short and stable.
|
|
90
80
|
- `instructions.ts` when you need to compose the prompt from typed helpers at build time.
|
|
91
|
-
- `skills/` for optional procedures that should load only when needed.
|
|
92
|
-
-
|
|
81
|
+
- `skills/` for optional procedures that should load only when needed. Move long procedures here instead of into the always-on prompt.
|
|
82
|
+
- `tools/` to expose typed integrations.
|
|
83
|
+
- a subagent when the task needs a different specialist surface; use one only for real specialization boundaries.
|
|
93
84
|
- the workspace or sandbox when the model should inspect files or run commands instead of relying on pasted instructions.
|
|
94
85
|
|
|
95
|
-
## A good pattern
|
|
96
|
-
|
|
97
|
-
For most agents:
|
|
98
|
-
|
|
99
|
-
1. Keep `instructions.md` short and stable.
|
|
100
|
-
2. Move long procedures into `skills/`.
|
|
101
|
-
3. Expose typed integrations through `tools/`.
|
|
102
|
-
4. Use subagents only for real specialization boundaries.
|
|
103
|
-
|
|
104
86
|
## What to read next
|
|
105
87
|
|
|
106
|
-
- [Tools](../tools)
|
|
107
|
-
- [Skills](../skills)
|
|
108
|
-
- [Subagents](../subagents)
|
|
109
|
-
- [
|
|
88
|
+
- [Tools](../tools): expose typed integrations the model can call.
|
|
89
|
+
- [Skills](../skills): the full authoring model for on-demand procedures.
|
|
90
|
+
- [Subagents](../subagents): delegate to a specialist with its own prompt and tools.
|
|
91
|
+
- [Dynamic capabilities](../guides/dynamic-capabilities): resolve per-session instructions and skills with `defineDynamic`.
|
|
92
|
+
- [Hooks](../guides/hooks): run code on session events to update the channel state that dynamic resolvers read.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "The Harness"
|
|
3
|
-
description: "The out-of-the-box agent loop and the built-in tools every
|
|
3
|
+
description: "The out-of-the-box Eve agent loop and the built-in tools every agent ships with, plus how to override or disable them."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The default harness is what every Eve agent ships with
|
|
6
|
+
The default harness is what every Eve agent ships with. It includes the framework-owned agent loop plus a set of built-in tools the model can call without you writing a line. You extend it with capabilities specific to your agent. The loop itself, how a turn runs and checkpoints and resumes, lives in [Execution model and durability](./execution-model-and-durability).
|
|
7
7
|
|
|
8
8
|
## Compaction
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
The harness keeps a long session from overflowing the model's context window. Once the conversation crosses a fraction of the window (`thresholdPercent`, `0.9` by default), it summarizes the older turns into a compact form and keeps going. The summary uses the active turn model unless you override it. Tune when and how it kicks in under [`compaction`](../agent-config#compaction) in `agent.ts`:
|
|
11
11
|
|
|
12
12
|
```ts title="agent/agent.ts"
|
|
13
13
|
export default defineAgent({
|
|
@@ -18,11 +18,11 @@ export default defineAgent({
|
|
|
18
18
|
});
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Compaction also preserves the framework's own tool state automatically.
|
|
21
|
+
Compaction also preserves the framework's own tool state automatically. It resets read-before-write tracking (so a write afterward re-reads the file whose read evidence was summarized away) and re-injects the active todo list, so the model keeps its task list across the summary. There is no per-tool hook to configure.
|
|
22
22
|
|
|
23
23
|
## Built-in tools
|
|
24
24
|
|
|
25
|
-
These ship with every agent, no imports.
|
|
25
|
+
These ship with every agent, no imports. The harness shows the model the tool descriptors first, then executes only what the model actually calls; discovery never runs them. The shell and file tools (`bash`, `read_file`, `write_file`, `glob`, `grep`) live in the app runtime and proxy their work into the agent's single [sandbox](../sandbox); the rest run in the app runtime. The "Where it runs" column below names where each tool's effect lands.
|
|
26
26
|
|
|
27
27
|
| Tool | Does | Where it runs |
|
|
28
28
|
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
|
@@ -46,9 +46,11 @@ Notes:
|
|
|
46
46
|
- **`connection_search`** is the model-facing `connection__search` tool. A search surfaces a connection's tools by their qualified name (e.g. `connection__linear__list_issues`), and the model can then call them directly. It's registered only when the agent has connections.
|
|
47
47
|
- **`web_search`** has no local executor; the provider runs it. To supply your own implementation, override it with `defineTool()`.
|
|
48
48
|
|
|
49
|
+
Review these built-in tools before production use. Disable, wrap, restrict, or require approval for any tool that can access the filesystem, network, shell, or sensitive data.
|
|
50
|
+
|
|
49
51
|
## Override a default
|
|
50
52
|
|
|
51
|
-
Author a tool at the same slug and it takes over the built-in of that name. The file `agent/tools/write_file.ts` replaces the built-in `write_file`
|
|
53
|
+
Author a tool at the same slug and it takes over the built-in of that name. The file `agent/tools/write_file.ts` replaces the built-in `write_file` by existing:
|
|
52
54
|
|
|
53
55
|
```ts title="agent/tools/write_file.ts"
|
|
54
56
|
import { defineTool } from "eve/tools";
|
|
@@ -63,7 +65,7 @@ export default defineTool({
|
|
|
63
65
|
});
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
The framework defaults are importable from `eve/tools/defaults` (`bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, `loadSkill`), so you can spread, wrap, or patch them. Skip the spread and your replacement owns its own context
|
|
68
|
+
The framework defaults are importable from `eve/tools/defaults` (`bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, `loadSkill`), so you can spread, wrap, or patch them. Skip the spread and your replacement owns its own context. A fresh `defineTool` for `todo` won't inherit the framework's durable state key.
|
|
67
69
|
|
|
68
70
|
## Disable a default
|
|
69
71
|
|
|
@@ -75,11 +77,19 @@ import { disableTool } from "eve/tools";
|
|
|
75
77
|
export default disableTool();
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
If the filename matches no known framework tool, resolution fails instead of silently doing nothing, so a typo surfaces at build time rather than removing the wrong tool.
|
|
81
|
+
|
|
82
|
+
## When to override, disable, or author a new tool
|
|
83
|
+
|
|
84
|
+
Three moves shape the harness. The right one depends on whether the model should keep the built-in capability.
|
|
85
|
+
|
|
86
|
+
- **Override** when you want the same capability with different behavior. Spread the default from `eve/tools/defaults` and wrap it (logging, an extra guard, a different backend), and the model still sees a tool by that name. Spreading keeps the default's description, schema, and any framework state, such as the `todo` tool's durable state key. Drop the spread and your replacement owns its own context, losing that wiring.
|
|
87
|
+
- **Disable** when the model should not have the capability at all. A `disableTool()` sentinel removes the built-in, and the model never sees it. Reach for this to lock down `bash` or `web_fetch` in an agent that should not run shell commands or fetch arbitrary URLs.
|
|
88
|
+
- **Author a new tool** when you want a capability the harness does not ship. Give it a fresh slug under `agent/tools/` and it joins the built-ins instead of replacing one. See [Tools](../tools) for the authoring model.
|
|
79
89
|
|
|
80
90
|
## The opt-in `Workflow` tool
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
An experimental `Workflow` tool ships but stays off by default. To turn it on, re-export the opt-in marker from `agent/tools/workflow.ts`:
|
|
83
93
|
|
|
84
94
|
```ts
|
|
85
95
|
export { ExperimentalWorkflow as default } from "eve/tools";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Execution Model
|
|
3
|
-
description: "How
|
|
2
|
+
title: "Execution Model and Durability"
|
|
3
|
+
description: "How an Eve session runs. Durable conversations, turns that checkpoint at steps, and parked work that resumes later."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
An Eve session is a durable conversation. It can run for days and survives process restarts and redeploys without any work on your part. You write the capabilities (tools, instructions, channels) and Eve runs the loop.
|
|
7
7
|
|
|
8
8
|
## Sessions, turns, and steps
|
|
9
9
|
|
|
@@ -13,38 +13,38 @@ Work nests in three levels:
|
|
|
13
13
|
- **turn**: one user message and all the work it triggers (model calls, tool calls, reasoning) until the agent produces its response.
|
|
14
14
|
- **step**: a durable checkpoint inside a turn (one model call and the tool calls it makes).
|
|
15
15
|
|
|
16
|
-
Every turn runs as a durable workflow, built on the open-source [Workflow SDK](https://workflow-sdk.dev/) (Vercel Workflow when you deploy on Vercel). Eve checkpoints progress
|
|
16
|
+
Every turn runs as a durable workflow, built on the open-source [Workflow SDK](https://workflow-sdk.dev/) (Vercel Workflow when you deploy on Vercel). Eve checkpoints progress and serializes durable state at each step boundary. Your code runs inside a managed step, so tools, the sandbox, and subagents feel synchronous even though the session underneath them is durable.
|
|
17
17
|
|
|
18
18
|
## Resuming after a crash
|
|
19
19
|
|
|
20
|
-
Crash the process, hit a timeout, or redeploy mid-turn, and the run picks up from the last completed step rather than replaying the whole turn.
|
|
20
|
+
Crash the process, hit a timeout, or redeploy mid-turn, and the run picks up from the last completed step rather than replaying the whole turn. 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.
|
|
21
21
|
|
|
22
|
-
There's nothing to configure
|
|
22
|
+
There's nothing to configure. Eve owns the workflow lifecycle, and sessions are durable by default.
|
|
23
23
|
|
|
24
|
-
You don't write workflow code directly. Workflow primitives (`start()`, `resumeHook()`, etc.) are an implementation detail of Eve's runtime layer; channels, tools, and hooks never touch them.
|
|
24
|
+
You don't write workflow code directly. Workflow primitives (`start()`, `resumeHook()`, etc.) are an implementation detail of Eve's runtime layer; channels, tools, and hooks never touch them. Two surfaces give your own code session data: tools read the current session's metadata (id, turn, auth, parent lineage) via `ctx.session`, and [`defineState`](../guides/state) reads or writes session-scoped durable state. See [State](../guides/state) for the read/write model.
|
|
25
25
|
|
|
26
26
|
## Parked work
|
|
27
27
|
|
|
28
|
-
Some work has to wait
|
|
28
|
+
Some work has to wait, including a human approving a [tool](../tools), an interactive OAuth sign-in for a [connection](../connections), or a long-running [subagent](../subagents). At those points the turn parks durably. The workflow suspends and holds no compute until the input it's waiting on arrives (a click, a callback, a child completing), even if that's much later. When it does, the conversation picks up exactly where it left off.
|
|
29
29
|
|
|
30
30
|
## Message delivery and queueing
|
|
31
31
|
|
|
32
|
-
Eve does not maintain a durable FIFO queue of user messages for a session
|
|
32
|
+
Eve does not maintain a durable FIFO queue of user messages for a session. The `continuationToken` is a resume handle for the session's current workflow hook, not a general message-queue address.
|
|
33
33
|
|
|
34
|
-
When a session is waiting, a delivery to the current continuation token wakes the session and starts the next turn. When a turn is already active,
|
|
34
|
+
When a session is waiting, a delivery to the current continuation token wakes the session and starts the next turn. When a turn is already active, the hook may accept additional deliveries, but the runtime only drains them at specific workflow boundaries. If more than one delivery is ready when the driver checks, Eve may fold them into the next turn; that drain is best-effort and depends on workflow and transport timing.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
So don't rely on concurrent sends to the same session behaving like a typical ordered chat queue. For deterministic behavior, send one user turn at a time and wait for `session.waiting` before sending the next message to the same session. If your channel can receive bursts while the agent is working, keep your own per-session queue in the channel or app layer, then deliver the next message after the session parks again. Separate sessions still run independently.
|
|
37
37
|
|
|
38
38
|
## Subagents
|
|
39
39
|
|
|
40
40
|
A turn can hand work off to a [subagent](../subagents). Each subagent gets its own context and its own durable session; a declared subagent also gets its own sandbox, skills, and state. Nothing crosses the boundary implicitly.
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## How Eve orders session history
|
|
43
43
|
|
|
44
|
-
Conversation history within a session is append-only. Turns land in order, and the tool calls inside a turn (plus their results) keep their order too. Read a session back and you see
|
|
44
|
+
Conversation history within a session is append-only. Turns land in order, and the tool calls inside a turn (plus their results) keep their order too. Read a session back and you see events in the order they happened.
|
|
45
45
|
|
|
46
46
|
## What to read next
|
|
47
47
|
|
|
48
|
-
- [Sessions
|
|
48
|
+
- [Sessions and streaming](./sessions-runs-and-streaming): the handles you hold and the event stream you watch.
|
|
49
49
|
- [Security model](./security-model): the trust boundaries the runtime enforces.
|
|
50
50
|
- [State](../guides/state): durable per-session memory that persists across step boundaries.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Security Model"
|
|
3
|
-
description: "
|
|
3
|
+
description: "Eve's trust boundaries, where secrets live, how credentials reach hosts, and what fails closed by default."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Your agent runs across two contexts, with a trust boundary
|
|
6
|
+
Your Eve agent runs across two contexts, with a trust boundary between them and every secret kept on the trusted side. Use this mental model when deciding what an agent (and the model driving it) is allowed to reach.
|
|
7
7
|
|
|
8
8
|
## Trust boundaries
|
|
9
9
|
|
|
@@ -16,13 +16,37 @@ Your agent runs across two contexts, with a trust boundary drawn between them an
|
|
|
16
16
|
|
|
17
17
|
The app runtime is the trusted side. Your tool implementations, model calls, connections, state, and durable execution all run here, with `process.env` and full Node.js available. (On Vercel, this is a Vercel Function.)
|
|
18
18
|
|
|
19
|
-
The sandbox is the isolated side. The model runs shell commands there through the built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` tools. It gets its own `/workspace` filesystem, but no `process.env`, no secrets, and no path back into the app runtime. (On Vercel, each sandbox is a [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with hardware-level isolation.)
|
|
19
|
+
The sandbox is the isolated side. The model runs shell commands there through the built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` tools. It gets its own `/workspace` filesystem, but no `process.env`, no secrets, and no path back into the app runtime. (On Vercel, each sandbox is a [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with hardware-level isolation.) Only shell commands execute in the sandbox. Even the built-in `bash`/`read_file`/`write_file` tools live in the app runtime and _proxy_ into the sandbox. The model sees tool definitions and results, never your secrets.
|
|
20
20
|
|
|
21
21
|
A concrete trace makes the boundary clear. When the model calls a custom `charge_card` tool, its `execute` runs in the app runtime, reads `process.env.STRIPE_KEY`, calls Stripe, and returns `{ ok: true }`. The model sees only `{ ok: true }`: the key never leaves the app runtime, and nothing about the call touches the sandbox. The built-in `write_file` is the mirror image, running in the app runtime and proxying the write into the sandbox `/workspace`. Either way the model drives the work through tool calls and their results, never by holding a credential or reaching the runtime directly.
|
|
22
22
|
|
|
23
|
+
## Data flow at a glance
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
flowchart LR
|
|
27
|
+
User["User or channel provider"] --> Channel["Channel route and route auth"]
|
|
28
|
+
Channel --> Runtime["Eve app runtime and durable session"]
|
|
29
|
+
Runtime --> Model["Configured model provider or Vercel AI Gateway"]
|
|
30
|
+
Runtime --> Tools["Authored tools and connections"]
|
|
31
|
+
Tools --> Services["Customer-selected external services"]
|
|
32
|
+
Runtime --> Sandbox["Per-session sandbox"]
|
|
33
|
+
Sandbox --> Egress["Allowed sandbox network egress"]
|
|
34
|
+
Runtime --> Telemetry["Configured telemetry or eval provider"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Eve sends data where your agent configuration and runtime choices send it:
|
|
38
|
+
|
|
39
|
+
- Inbound channel data flows through the channel provider you configure, then into the Eve app runtime.
|
|
40
|
+
- Model inputs and outputs flow to the model or routing path selected in `agent.ts`, such as a Vercel AI Gateway model id or a provider-authored `LanguageModel`.
|
|
41
|
+
- Tool and connection calls flow to the external services, MCP servers, OpenAPI endpoints, and channels you configure.
|
|
42
|
+
- Sandbox commands can reach network destinations allowed by the sandbox network policy.
|
|
43
|
+
- Telemetry and eval data flows to the exporters and providers you configure in `instrumentation.ts` or eval settings.
|
|
44
|
+
|
|
45
|
+
Eve stores durable session and workflow state needed to resume conversations, stream events, replay completed steps, and show run observability. You are responsible for deciding whether the selected channels, model providers, connected services, sandbox egress destinations, telemetry exporters, retention settings, and deletion controls are appropriate for your data and use case.
|
|
46
|
+
|
|
23
47
|
## Credential brokering
|
|
24
48
|
|
|
25
|
-
|
|
49
|
+
Credential brokering gives the model _authenticated_ network access from inside the sandbox, like a `git clone` of a private repo or an authenticated `curl`, when there's no [tool](../tools) or [connection](../connections) to route it through. On the Vercel Sandbox backend, auth headers get injected at the sandbox's network firewall for matching domains. The secret stays in the app runtime; the sandbox process only ever sees the response. See [Vercel Sandbox Credential Brokering](https://vercel.com/docs/sandbox/concepts/firewall#credentials-brokering) for the platform mechanism, and [Sandbox](../sandbox) for the Eve policy API.
|
|
26
50
|
|
|
27
51
|
## Connection credentials
|
|
28
52
|
|
|
@@ -30,7 +54,7 @@ Sometimes the model needs _authenticated_ network access from inside the sandbox
|
|
|
30
54
|
|
|
31
55
|
## Channel verification
|
|
32
56
|
|
|
33
|
-
A [channel](../channels/overview) is your agent's front door,
|
|
57
|
+
A [channel](../channels/overview) is your agent's front door, so authenticating inbound traffic is its job. The built-in platform channels follow two rules, and so must any channel you write yourself:
|
|
34
58
|
|
|
35
59
|
- **Verify signatures in constant time.** Platform channels (Slack, GitHub,
|
|
36
60
|
Telegram, Twilio) verify the platform's HMAC signature over the raw request body
|
|
@@ -46,11 +70,11 @@ A custom channel that accepts dashboard-style webhooks should follow the same sh
|
|
|
46
70
|
|
|
47
71
|
## Authored markdown is data
|
|
48
72
|
|
|
49
|
-
[Skill](../skills) and [schedule](../schedules) files are markdown with YAML frontmatter, and Eve treats that frontmatter strictly as data. The code-capable engines (`---js` / `---javascript`, which would `eval()` the frontmatter body the moment the file is parsed) are disabled
|
|
73
|
+
[Skill](../skills) and [schedule](../schedules) files are markdown with YAML frontmatter, and Eve treats that frontmatter strictly as data. The code-capable engines (`---js` / `---javascript`, which would `eval()` the frontmatter body the moment the file is parsed) are disabled, so such a fence throws rather than running. Frontmatter has to parse to a plain YAML object.
|
|
50
74
|
|
|
51
75
|
## Auth fails closed
|
|
52
76
|
|
|
53
|
-
Routes reject unauthenticated traffic by default
|
|
77
|
+
Routes reject unauthenticated traffic by default. If no `AuthFn` in the walk accepts the request, it gets a `401`, and admitting anonymous callers takes an explicit `none()`. The scaffold's `placeholderAuth()` keeps a half-configured app closed in production until you replace it. See [Auth & route protection](../guides/auth-and-route-protection) for the full walk and verifiers.
|
|
54
78
|
|
|
55
79
|
## Pre-production checklist
|
|
56
80
|
|
|
@@ -75,5 +99,5 @@ Before exposing an agent to real traffic:
|
|
|
75
99
|
|
|
76
100
|
- [Auth & route protection](../guides/auth-and-route-protection): the full auth walk and verifier helpers
|
|
77
101
|
- [Sandbox](../sandbox): backends, network policy, and brokering config
|
|
78
|
-
- [Execution model
|
|
102
|
+
- [Execution model and durability](./execution-model-and-durability): how durable sessions run
|
|
79
103
|
- [Connections](../connections): static-token and OAuth connections
|
|
@@ -7,7 +7,7 @@ Every Eve app speaks the same stable HTTP API to a [durable session](./execution
|
|
|
7
7
|
|
|
8
8
|
## The two handles
|
|
9
9
|
|
|
10
|
-
Two handles do two jobs
|
|
10
|
+
Two handles do two jobs, and mixing them up is the most common mistake. One handle creates and resumes a session; a different one streams and inspects it.
|
|
11
11
|
|
|
12
12
|
- **`continuationToken`**: the resume handle. Use it to send a follow-up message to the same conversation. Owned by the channel.
|
|
13
13
|
- **`sessionId` / `runId`**: the stream-and-inspect handle. Use it to attach to the event stream and watch a run. Owned by the runtime.
|
|
@@ -19,17 +19,17 @@ React, Vue, and Svelte apps reach for [`useEveAgent()`](../guides/frontend/overv
|
|
|
19
19
|
## Start a session
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
curl -X POST http://127.0.0.1:
|
|
22
|
+
curl -X POST http://127.0.0.1:3000/eve/v1/session \
|
|
23
23
|
-H 'content-type: application/json' \
|
|
24
24
|
-d '{"message":"Summarize the latest forecast."}'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Eve responds right away. The JSON body carries a `sessionId` and a `continuationToken`, and the `x-eve-session-id` header
|
|
27
|
+
Eve responds right away. The JSON body carries a `sessionId` and a `continuationToken`, and the `x-eve-session-id` header names the durable session to stream.
|
|
28
28
|
|
|
29
29
|
## Stream a session
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
curl http://127.0.0.1:
|
|
32
|
+
curl http://127.0.0.1:3000/eve/v1/session/<sessionId>/stream
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
The stream is newline-delimited JSON (NDJSON), one event per line:
|
|
@@ -64,6 +64,8 @@ The stream is newline-delimited JSON (NDJSON), one event per line:
|
|
|
64
64
|
|
|
65
65
|
`reasoning.appended` and `message.appended` stream deltas as they arrive, and each one carries both the new delta and the cumulative text for the current block. The finalized block shows up on `message.completed` and `reasoning.completed`, which is the compatibility path for clients that don't render incremental streaming.
|
|
66
66
|
|
|
67
|
+
Note: consider the privacy, confidentiality, and user-experience implications for displaying, storing, or transmitting reasoning events in your application.
|
|
68
|
+
|
|
67
69
|
`message.completed` can fire more than once in a turn: the agent often emits interim assistant text before a tool call. To tell tool-call narration from a terminal reply, check `message.completed.data.finishReason`. `step.completed.data.finishReason` mirrors the step outcome, and usage lives on `step.completed`.
|
|
68
70
|
|
|
69
71
|
A delegated subagent publishes progress on its own child-session stream. The parent only emits `subagent.called` with a `childSessionId`, which a client uses to attach.
|
|
@@ -75,7 +77,7 @@ A delegated subagent publishes progress on its own child-session stream. The par
|
|
|
75
77
|
Once the session is waiting (you'll see `session.waiting`), POST your follow-up to the session endpoint with the stored continuation token:
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
|
-
curl -X POST http://127.0.0.1:
|
|
80
|
+
curl -X POST http://127.0.0.1:3000/eve/v1/session/<sessionId> \
|
|
79
81
|
-H 'content-type: application/json' \
|
|
80
82
|
-d '{"continuationToken":"<token>","message":"Now send the short version."}'
|
|
81
83
|
```
|
|
@@ -86,10 +88,10 @@ For deterministic ordering, send one follow-up at a time and wait for the next `
|
|
|
86
88
|
|
|
87
89
|
## Reconnect and rewind
|
|
88
90
|
|
|
89
|
-
The stream is durable. Every event is recorded before a step completes, so
|
|
91
|
+
The stream is durable. Every event is recorded before a step completes, so the whole stream is replayable. Pass `startIndex` to reconnect by event count and pick up where you dropped off, or rewind to the start:
|
|
90
92
|
|
|
91
93
|
```bash
|
|
92
|
-
curl "http://127.0.0.1:
|
|
94
|
+
curl "http://127.0.0.1:3000/eve/v1/session/<sessionId>/stream?startIndex=<count>"
|
|
93
95
|
```
|
|
94
96
|
|
|
95
97
|
## Use the client from TypeScript
|
|
@@ -103,7 +105,7 @@ Start with the [TypeScript SDK](../guides/client/overview) guide. It covers basi
|
|
|
103
105
|
`GET /eve/v1/info` returns a JSON inspection snapshot for the running agent: model, instructions, authored and framework tools, skills, channels, schedules, subagents, sandbox, connections, hooks, workflow, and workspace metadata. Local development accepts loopback requests; deployed Vercel targets require the route's OIDC auth.
|
|
104
106
|
|
|
105
107
|
```bash
|
|
106
|
-
curl http://127.0.0.1:
|
|
108
|
+
curl http://127.0.0.1:3000/eve/v1/info
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
The route uses the same default auth chain as the eve channel (`[localDev(), vercelOidc()]`). Locally it answers anonymously; a deployed Vercel target requires a valid OIDC bearer, with a same-project bypass for in-deployment callers. See [auth & route protection](../guides/auth-and-route-protection).
|
|
@@ -117,7 +119,7 @@ Every stream event runs four steps, in this order:
|
|
|
117
119
|
3. **Hooks**: authored [hooks](../guides/hooks) subscribed to the event fire.
|
|
118
120
|
4. **Dynamic resolvers**: [dynamic](../guides/dynamic-capabilities) tool, skill, and instruction resolvers fire, and `ctx.channel.metadata` already holds the freshly projected metadata from step 2.
|
|
119
121
|
|
|
120
|
-
The order
|
|
122
|
+
The order is structural, not incidental. By the time a resolver or hook reads channel metadata, the channel has already updated its state and the projection is current.
|
|
121
123
|
|
|
122
124
|
## What to read next
|
|
123
125
|
|
package/docs/connections.mdx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Connections"
|
|
3
|
-
description: "Expose external MCP and OpenAPI servers to the model
|
|
3
|
+
description: "Expose external MCP and OpenAPI servers to the model, with connection tokens the model never sees."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
A connection wires an agent into an external server you don't author
|
|
6
|
+
A connection wires an agent into an external server you don't author, either an MCP server (Linear, GitHub, a warehouse) or any HTTP API with an OpenAPI document. Eve handles the parts you'd otherwise hand-roll, discovering the remote tools, surfacing them to the model, and brokering auth.
|
|
7
7
|
|
|
8
8
|
Connections live under `agent/connections/`. The runtime name comes from the filename, so `agent/connections/linear.ts` registers as `"linear"`. The model never sees a connection's URL or credentials. It discovers tools through the built-in `connection__search` and calls them by their qualified name, `connection__<connection>__<tool>` (e.g. `connection__linear__list_issues`).
|
|
9
9
|
|
|
10
10
|
## MCP connections
|
|
11
11
|
|
|
12
|
-
`defineMcpClientConnection` points at an MCP server.
|
|
12
|
+
`defineMcpClientConnection` points at an MCP server. Supply a `url` and a `description`:
|
|
13
13
|
|
|
14
14
|
```ts title="agent/connections/linear.ts"
|
|
15
15
|
import { defineMcpClientConnection } from "eve/connections";
|
|
@@ -23,11 +23,11 @@ export default defineMcpClientConnection({
|
|
|
23
23
|
});
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model
|
|
26
|
+
The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model uses it to decide which connection to query.
|
|
27
27
|
|
|
28
28
|
### Static-token auth
|
|
29
29
|
|
|
30
|
-
`getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets
|
|
30
|
+
`getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets, including an env var, a secrets manager, an internal vault, or your own OAuth exchange. If the token has a known TTL, set `expiresAt` (milliseconds since epoch) and Eve refreshes ahead of time rather than waiting for a `401`.
|
|
31
31
|
|
|
32
32
|
When `getToken` is the only auth, `principalType` defaults to `"app"`: one shared credential keyed across all sessions. Switch to `principalType: "user"` when each end-user carries their own token.
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ Eve resolves and caches connection tokens per step; they never land in conversat
|
|
|
35
35
|
|
|
36
36
|
### No auth
|
|
37
37
|
|
|
38
|
-
Drop `auth` entirely for servers that need no token,
|
|
38
|
+
Drop `auth` entirely for servers that need no token, such as a localhost server during development or a public one:
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
export default defineMcpClientConnection({
|
|
@@ -44,9 +44,11 @@ export default defineMcpClientConnection({
|
|
|
44
44
|
});
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
We recommend using no-auth connections only for services that are intentionally public, local-only, or otherwise protected outside Eve. Do not use no-auth connections for sensitive third-party services.
|
|
48
|
+
|
|
47
49
|
### Headers
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
Use `headers` when the server wants a non-Bearer scheme (an API-key header) or extra configuration. Headers stack on top of `auth`:
|
|
50
52
|
|
|
51
53
|
```ts
|
|
52
54
|
export default defineMcpClientConnection({
|
|
@@ -58,7 +60,7 @@ export default defineMcpClientConnection({
|
|
|
58
60
|
|
|
59
61
|
### Tool filters
|
|
60
62
|
|
|
61
|
-
To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools
|
|
63
|
+
To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools do not appear in `connection__search`:
|
|
62
64
|
|
|
63
65
|
```ts
|
|
64
66
|
export default defineMcpClientConnection({
|
|
@@ -84,11 +86,13 @@ export default defineMcpClientConnection({
|
|
|
84
86
|
});
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
`never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume
|
|
89
|
+
`never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume is the same human-in-the-loop flow covered in [Tools](./tools).
|
|
90
|
+
|
|
91
|
+
For connection tools that can create, modify, delete, transmit, purchase, message, or access sensitive data, use approval, tool allow-lists, or other safeguards appropriate to the action.
|
|
88
92
|
|
|
89
93
|
## OpenAPI connections
|
|
90
94
|
|
|
91
|
-
`defineOpenAPIConnection` turns any OpenAPI 3.x
|
|
95
|
+
`defineOpenAPIConnection` turns any OpenAPI 3.x document into connection tools, one per operation. Pass an HTTPS URL Eve fetches at runtime, or an inline parsed object:
|
|
92
96
|
|
|
93
97
|
```ts title="agent/connections/petstore.ts"
|
|
94
98
|
import { defineOpenAPIConnection } from "eve/connections";
|
|
@@ -104,10 +108,10 @@ Each operation becomes `connection__<connection>__<operationId>` (e.g. `connecti
|
|
|
104
108
|
|
|
105
109
|
`auth`, `headers`, and `approval` work exactly as they do for MCP. There are two fields specific to OpenAPI:
|
|
106
110
|
|
|
107
|
-
| Field | Purpose
|
|
108
|
-
| ------------ |
|
|
109
|
-
| `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry
|
|
110
|
-
| `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools.
|
|
111
|
+
| Field | Purpose |
|
|
112
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry. |
|
|
114
|
+
| `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools. |
|
|
111
115
|
|
|
112
116
|
## Interactive OAuth via Vercel Connect
|
|
113
117
|
|
|
@@ -128,7 +132,7 @@ export default defineMcpClientConnection({
|
|
|
128
132
|
|
|
129
133
|
## Self-hosted interactive OAuth
|
|
130
134
|
|
|
131
|
-
|
|
135
|
+
To run your own OAuth, use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form and needs no Vercel Connect. Eve mints a callback URL, parks (durably suspends) the turn on a framework-owned webhook, and resumes once the token comes back. Interactive auth is always `principalType: "user"`, and the factory pins that for you.
|
|
132
136
|
|
|
133
137
|
```ts title="agent/connections/linear.ts"
|
|
134
138
|
import {
|
|
@@ -168,9 +172,18 @@ export default defineMcpClientConnection({
|
|
|
168
172
|
});
|
|
169
173
|
```
|
|
170
174
|
|
|
171
|
-
`getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event.
|
|
175
|
+
`getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event. Its fields:
|
|
176
|
+
|
|
177
|
+
| Field | Purpose |
|
|
178
|
+
| -------------- | ----------------------------------------------------------------------------------------- |
|
|
179
|
+
| `url` | The authorize URL for redirect or device flows. |
|
|
180
|
+
| `userCode` | The device code, for device flows. |
|
|
181
|
+
| `instructions` | The call to action when there's no URL. |
|
|
182
|
+
| `displayName` | Human-readable provider name channels show on the sign-in affordance (e.g. "Salesforce"). |
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
Drop `resume` when the provider keeps flow state server-side, so nothing has to cross the step boundary.
|
|
185
|
+
|
|
186
|
+
`displayName` is presentation-only. The connection's path-derived name still keys the authorization scope, token cache, and callback URL. You can also set `displayName` on the `auth` definition itself (e.g. `auth: { ...connect("sfdc"), displayName: "Salesforce" }`); that definition-level value wins over one the strategy stamps on the challenge, and channels fall back to title-casing the connection name when neither is set.
|
|
174
187
|
|
|
175
188
|
### Signaling authorization state
|
|
176
189
|
|
|
@@ -192,24 +205,31 @@ To narrow a caught error, use `isConnectionAuthorizationRequiredError(err)` and
|
|
|
192
205
|
|
|
193
206
|
### Handling a revoked token mid-call
|
|
194
207
|
|
|
195
|
-
`getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is
|
|
208
|
+
`getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is only a tool error, so the model sees a failure and the cached bearer sticks around. Instead, map a provider `401` to `ctx.requireAuth()` (or rethrow `ConnectionAuthorizationRequiredError`). Eve then evicts the rejected token from its per-step cache and re-runs the consent flow with a fresh one, exactly as it does for a connection whose server rejects the bearer.
|
|
196
209
|
|
|
197
210
|
```ts title="agent/tools/list_issues.ts"
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
211
|
+
import { defineTool } from "eve/tools";
|
|
212
|
+
import { z } from "zod";
|
|
213
|
+
|
|
214
|
+
export default defineTool({
|
|
215
|
+
description: "List open Linear issues.",
|
|
216
|
+
inputSchema: z.object({}),
|
|
217
|
+
async execute(_input, ctx) {
|
|
218
|
+
const { token } = await ctx.getToken();
|
|
219
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
220
|
+
headers: { authorization: `Bearer ${token}` },
|
|
221
|
+
});
|
|
222
|
+
// The grant was revoked since getToken ran: re-challenge instead of
|
|
223
|
+
// returning a dead-token error to the model.
|
|
224
|
+
if (res.status === 401) ctx.requireAuth();
|
|
225
|
+
return await res.json();
|
|
226
|
+
},
|
|
227
|
+
});
|
|
208
228
|
```
|
|
209
229
|
|
|
210
230
|
### Authorization and approval together
|
|
211
231
|
|
|
212
|
-
A tool can require both sign-in (`auth`) and a human approval.
|
|
232
|
+
A tool can require both sign-in (`auth`) and a human approval. The model's approval gate runs before the tool's `execute`, so the order the user sees is **approve, then sign in**. Eve records the approval on session state the moment it's granted, and that record survives the sign-in park, so when the turn resumes after authorization the tool is not put through approval again. You get one approval and one sign-in, never a double prompt.
|
|
213
233
|
|
|
214
234
|
## What to read next
|
|
215
235
|
|