Skip to content

Add Vercel Eve as a third agent framework#179

Open
ryw wants to merge 4 commits into
mainfrom
eve-framework
Open

Add Vercel Eve as a third agent framework#179
ryw wants to merge 4 commits into
mainfrom
eve-framework

Conversation

@ryw

@ryw ryw commented Jun 17, 2026

Copy link
Copy Markdown
Member

Adds Eve (github.com/vercel/eve) as a third agent framework alongside Pydantic AgentSpec and Cargo AI. An Eve agent committed at agents/eve/<name>/ is listed like any other agent and Run (plus scheduler / Slack / webhook / REST / Composio triggers) executes it — one turn, locally, in-process, with no Eve HTTP server — capturing reply + token usage into the run/run_step rows exactly like the other frameworks.

Why this was non-trivial

Eve isn't a single-file spec — it's a directory of TypeScript files, and it has no public server-less one-shot execution mode (everything routes through its HTTP app + the Vercel Workflow SDK). A spike proved we can run a turn fully in-process with no listening socket by building the agent, patching out the Nitro serve() call to expose the fetch handler, pointing the Workflow SDK's local backend at a sentinel host, and looping its callbacks back through globalThis.fetch. See api/scripts/EVE_SPIKE_NOTES.md.

What's in here

Backend (execution path)

  • api/scripts/run_eve.mjs — the harness: materialize the agent dir → pnpm installeve build → patch serve() → workflow loopback → drive via eve/client → emit __TAS_USAGE__/__TAS_STEPS__.
  • api/src/runs/eve.rs — spawns the harness, reuses the Pydantic sentinel parsers.
  • runner.rs / handlers.rs / mod.rsFramework::Eve dispatch, RunContext.agent_files, CreateRunRequest.agent_files, parse_framework "eve".
  • api/DockerfileNode 24 + pnpm@10 + the harness + a persistent pnpm store. (Adds a third language runtime to the API image — see review note.)

Web (directory-as-agent modeling)

  • Registry (FRAMEWORKS/labels/AgentFramework), EveSpec + buildEveSpec/extractEveModel.
  • workspace-agents.ts: readEveAgentDir, listEveAgents (scan subdirs), getAgentByName + resolveAgentForDispatch eve branches (live dir → agentFiles, no stable snapshot).
  • runs-api.ts agentFilesagent_files, threaded through every dispatch consumer.
  • CAP (cap-api.ts), agent-guidance.ts Eve guide, docs.

Critical authoring rule (baked into the Eve guide)

TAS runs with the workspace's Anthropic/OpenAI key and no AI Gateway credentials, so Eve agents must use a direct @ai-sdk provider model (model: anthropic("claude-opus-4-8")), not the default gateway string.

Validated locally (all green)

  • cargo test — 38 pass; cargo check clean
  • web tsc --noEmit — 0 errors
  • web eslint — 0 errors
  • web vitest — 101 pass
  • The spike ran a real Eve turn end-to-end in-process (text + token usage) with live API keys.

NOT yet verified — test plan before merge

  • Build the API Docker image (the new Node-24 layer) — unbuilt locally.
  • End-to-end on the internal Railway instance: commit a sample agents/eve/hello/ (direct-provider model), Run it, confirm output + token/step usage + cost render like a Pydantic run; confirm a second run hits the pnpm store cache.
  • Confirm pnpm install at runtime works as the non-root tas user (HOME=/tmp, /opt/eve-store).

v1 scope (flagged in code + docs)

  • Eve agents always run the live directory (no versioned promotion); chat-run is disabled for them (use Run).
  • Tool-using agents rely on Eve's local sandbox (just-bash); no Composio/native-MCP credential injection (Eve manages its own).

Review note

This adds Node 24 + pnpm to the production API container and runs agent-authored TypeScript at run time (acceptable on TAS's single-tenant, customer-owned deployment, but worth a conscious look). If you'd prefer Eve execution in a separate sidecar/image rather than the main API container, that's a reasonable alternative the web dispatch could target instead.

🤖 Generated with Claude Code

Comment thread api/scripts/run_eve.mjs
}

function installDeps(root) {
const env = { ...process.env };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This env inherits OPENAI_API_KEY and ANTHROPIC_API_KEY, and buildProject falls back to process.env too. That means arbitrary preinstall/postinstall scripts or build-time code from the agent's dependencies can read provider secrets before the turn starts. Scrub provider keys from the env used for install/build, then expose them only when sending the session.

Comment thread api/scripts/run_eve.mjs
function installDeps(root) {
const env = { ...process.env };
if (process.env.TAS_EVE_PNPM_STORE) env.PNPM_HOME = process.env.TAS_EVE_PNPM_STORE;
const args = ["install", "--ignore-workspace", "--prod=false"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime installs should be locked to the committed pnpm-lock.yaml; otherwise a run can resolve newer transitive deps or rewrite the lockfile in the temp dir and become non-reproducible.

Suggested change
const args = ["install", "--ignore-workspace", "--prod=false"];
const args = ["install", "--ignore-workspace", "--prod=false", "--frozen-lockfile"];

Comment thread api/scripts/run_eve.mjs
const d = ev?.data ?? {};
if (t === "message.completed" && typeof d.message === "string") {
text = d.message;
} else if (t === "message.appended" && typeof d.messageSoFar === "string" && !text) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Eve ever omits message.completed, this fallback keeps only the first appended chunk because text becomes truthy after the first event. Keep replacing it with the cumulative messageSoFar so the fallback returns the full reply.

Suggested change
} else if (t === "message.appended" && typeof d.messageSoFar === "string" && !text) {
} else if (t === "message.appended" && typeof d.messageSoFar === "string") {

Comment thread api/scripts/run_eve.mjs
const files = readFilesEnv();
const message = process.env.TAS_MESSAGE && process.env.TAS_MESSAGE.length ? process.env.TAS_MESSAGE : "Hello.";

const root = materialize(files);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each run leaves the materialized project, installed node_modules, .output, and workflow data under /tmp. In a long-running API container this will steadily consume disk; wrap the run in a finally and remove both temp dirs after emitting the result.

// in-process, with no Vercel deploy and no AI Gateway — so the single
// non-negotiable rule is: configure the model with a DIRECT @ai-sdk provider,
// not the gateway string. Everything else is standard Eve.
const EVE_GUIDE: string = `# Eve Agent Guide (Tembo Agent Studio)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EVE_GUIDE is stamped with TAS_GUIDANCE_VERSION, but that hash is computed above without this string. After the first write, future edits to the Eve guide will keep the same marker and the sync path will treat stale files as current. Move this guide above the hash and include it in the digest.

const { dir, ext } = FRAMEWORK_PATH[framework];
const agentPath = `agents/${dir}/${agentSlug}.${ext}`;
const agentPath =
framework === "eve"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that eve can reach buildCreateAgentPrompt, it still gets the single-file spec instructions for name:, YAML connections:, and model: provider:model, which conflicts with Eve's directory/TypeScript shape. Branch the create prompt for Eve or gate the Pydantic/Cargo-specific text before enabling this option.

ryw and others added 4 commits June 17, 2026 23:52
Validates that an Eve agent can run a turn one-shot, fully in-process with no
listening socket, returning text + token usage — by building the Nitro output,
patching out serve() to expose the fetch handler, running the durable workflow
via WORKFLOW_LOCAL_* + a globalThis.fetch loopback, public channel auth, and a
direct @ai-sdk provider. Captures the recipe (EVE_SPIKE_NOTES.md) and the working
driver for the production harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend half of the Eve framework integration (execution path):
- api/scripts/run_eve.mjs: in-process one-shot harness (materialize agent dir,
  pnpm install, eve build, patch out serve(), local workflow loopback, drive via
  eve/client, emit __TAS_USAGE__/__TAS_STEPS__) — implements the proven spike.
- api/src/runs/eve.rs: spawns the harness, reuses the pydantic sentinel parsers.
- runner.rs/handlers.rs/mod.rs: Framework::Eve + dispatch + RunContext.agent_files
  + CreateRunRequest.agent_files + parse_framework "eve".
- api/Dockerfile: Node 24 + pnpm@10 + run_eve.mjs + persistent pnpm store.

cargo check passes. Web touch points (framework registry, readEveAgentDir,
CAP scaffolding, docs) follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make Eve a first-class framework in the web layer (tsc + eslint clean):
- registry: "eve" in FRAMEWORKS/FRAMEWORK_LABELS, AgentFramework union
- agent-format: EveSpec + buildEveSpec/extractEveModel (directory, not a parsed
  single file)
- workspace-agents: FRAMEWORK_DIRS eve, readEveAgentDir, listEveAgents (scan
  subdirs), getAgentByName + resolveAgentForDispatch eve branches (live dir →
  agentFiles, no stable snapshot)
- runs-api: agentFiles → agent_files; framework union
- thread agentFiles through every dispatch consumer (run-now, webhook, slack,
  api-v1, composio, scheduler); guard chat-run + promote for eve (v1 scope)
- FRAMEWORK_PATH eve entries with directory-aware entrypoint path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- agent-guidance: EVE_GUIDE + GUIDANCE_EVE_PATH, wired into guidanceFilesFor /
  allGuidanceFiles. The guide's load-bearing rule: use a direct @ai-sdk provider
  model, not the AI Gateway string (TAS has no Gateway creds).
- cap-api: frameworkFromAgentPath detects agents/eve/; frameworkGuidePath helper
  points create + chat prompts at the Eve guide.
- docs: "Eve agents (experimental)" section in authoring-agents.

Full branch green: cargo test (38) + tsc + eslint + vitest (101) all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ryw ryw force-pushed the eve-framework branch from af14f3b to 7902721 Compare June 18, 2026 03:52
@ryw ryw closed this Jul 8, 2026
@ryw ryw deleted the eve-framework branch July 8, 2026 15:00
@ryw ryw restored the eve-framework branch July 8, 2026 15:01
@ryw ryw reopened this Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant