Agent-readable wiki
Eve vs Flue — Agent Harness Comparison
A source-backed comparison of vercel/eve and withastro/flue: two TypeScript frameworks for durable, tool-using agents that share skills, sandboxes, and channels—but diverge sharply in authoring model, runtime layering, deployment posture, and how workflows enter the picture.
Pages
- Comparison FrameWhat Eve and Flue each optimize for, the shared problem space (durable agents with tools, skills, sandboxes, and channels), and the criteria—authoring ergonomics, runtime layering, deployment portability, and workflow boundaries—that make cross-repo lessons actionable.
- Eve vs Flue Runtime SplitsContrasts Eve's three-way split—channel owns continuationToken, harness does one AI unit, runtime persists and streams—with Flue's harness → session → operation hierarchy and its explicit agents-versus-workflows boundary.
- Eve Filesystem Slots & Compiled ArtifactsHow Eve treats the agent/ directory as the contract: path-derived identities for tools, skills, channels, and subagents; defineAgent for additive runtime config; and compilation into inspectable .eve/ runtime artifacts.
- Flue createAgent & Agent/Workflow SplitHow Flue authors agents as TypeScript modules with createAgent, defineAgentProfile, and explicit skill/tool imports; how workflows export run(ctx) for finite executions; and how the CLI build graph discovers agents/ and workflows/ layouts.
- Durable Execution & CheckpointingCompares Eve's workflow-backed session/turn/step model with automatic step checkpoints and parked work, against Flue's pluggable run and agent-execution stores, dispatch queue, and workflow run IDs exposed over HTTP.
- Channels, Ingress & Conversation HandlesEve channels as authored agent/channels/ routes that own continuationToken and auth policy, versus Flue's first-party channel packages (@flue/slack, etc.), blueprints from flue add, and dispatch-based agent admission over a Hono HTTP surface.
- Sandboxes, Built-in Tools & SkillsEve's single per-agent sandbox with shipped harness tools (bash, read_file, load_skill, connection_search) and sandbox proxying, contrasted with Flue's adapter-based sandbox factories (local, Cloudflare, blueprint-backed remote) and explicit tool/skill composition in createAgent.
- Comparison Verdict & Portable IdeasClosing synthesis: where Eve wins (Vercel-native durability, filesystem discoverability, opinionated harness defaults) versus where Flue wins (multi-target deploy, explicit workflow surface, channel package ecosystem); which patterns—skills, sandbox isolation, session streaming—transfer across either stack.
Complete Markdown
# Eve vs Flue — Agent Harness Comparison
> A source-backed comparison of vercel/eve and withastro/flue: two TypeScript frameworks for durable, tool-using agents that share skills, sandboxes, and channels—but diverge sharply in authoring model, runtime layering, deployment posture, and how workflows enter the picture.
## Context Links
- [Agent index](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681)
- [GitHub repository](https://github.com/vercel/eve)
## Repository Metadata
- Repository: vercel/eve-with-withastro-flue
- Generated: 2026-06-18T19:37:56.498Z
- Updated: 2026-06-18T19:38:10.916Z
- Runtime: Grok CLI
- Format: Repo Comparison
- Pages: 8
## Page Index
- 01. [Comparison Frame](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/01-comparison-frame.md) - What Eve and Flue each optimize for, the shared problem space (durable agents with tools, skills, sandboxes, and channels), and the criteria—authoring ergonomics, runtime layering, deployment portability, and workflow boundaries—that make cross-repo lessons actionable.
- 02. [Eve vs Flue Runtime Splits](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/02-eve-vs-flue-runtime-splits.md) - Contrasts Eve's three-way split—channel owns continuationToken, harness does one AI unit, runtime persists and streams—with Flue's harness → session → operation hierarchy and its explicit agents-versus-workflows boundary.
- 03. [Eve Filesystem Slots & Compiled Artifacts](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/03-eve-filesystem-slots-compiled-artifacts.md) - How Eve treats the agent/ directory as the contract: path-derived identities for tools, skills, channels, and subagents; defineAgent for additive runtime config; and compilation into inspectable .eve/ runtime artifacts.
- 04. [Flue createAgent & Agent/Workflow Split](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/04-flue-createagent-agent-workflow-split.md) - How Flue authors agents as TypeScript modules with createAgent, defineAgentProfile, and explicit skill/tool imports; how workflows export run(ctx) for finite executions; and how the CLI build graph discovers agents/ and workflows/ layouts.
- 05. [Durable Execution & Checkpointing](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/05-durable-execution-checkpointing.md) - Compares Eve's workflow-backed session/turn/step model with automatic step checkpoints and parked work, against Flue's pluggable run and agent-execution stores, dispatch queue, and workflow run IDs exposed over HTTP.
- 06. [Channels, Ingress & Conversation Handles](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/06-channels-ingress-conversation-handles.md) - Eve channels as authored agent/channels/ routes that own continuationToken and auth policy, versus Flue's first-party channel packages (@flue/slack, etc.), blueprints from flue add, and dispatch-based agent admission over a Hono HTTP surface.
- 07. [Sandboxes, Built-in Tools & Skills](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/07-sandboxes-built-in-tools-skills.md) - Eve's single per-agent sandbox with shipped harness tools (bash, read_file, load_skill, connection_search) and sandbox proxying, contrasted with Flue's adapter-based sandbox factories (local, Cloudflare, blueprint-backed remote) and explicit tool/skill composition in createAgent.
- 08. [Comparison Verdict & Portable Ideas](https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/08-comparison-verdict-portable-ideas.md) - Closing synthesis: where Eve wins (Vercel-native durability, filesystem discoverability, opinionated harness defaults) versus where Flue wins (multi-target deploy, explicit workflow surface, channel package ecosystem); which patterns—skills, sandbox isolation, session streaming—transfer across either stack.
## Source File Index
- `vercel-eve:apps/frameworks/nuxt/agent/agent.ts`
- `vercel-eve:apps/frameworks/nuxt/agent/channels/eve.ts`
- `vercel-eve:apps/frameworks/nuxt/agent/instructions.md`
- `vercel-eve:Dockerfile`
- `vercel-eve:docs/agent-config.md`
- `vercel-eve:docs/channels/custom.mdx`
- `vercel-eve:docs/channels/overview.mdx`
- `vercel-eve:docs/concepts/default-harness.md`
- `vercel-eve:docs/concepts/execution-model-and-durability.md`
- `vercel-eve:docs/concepts/security-model.md`
- `vercel-eve:docs/concepts/sessions-runs-and-streaming.md`
- `vercel-eve:docs/reference/project-layout.md`
- `vercel-eve:docs/sandbox.mdx`
- `vercel-eve:package.json`
- `vercel-eve:packages/eve/package.json`
- `vercel-eve:packages/eve/src/client/session.ts`
- `vercel-eve:packages/eve/src/compiler/compile-agent.ts`
- `vercel-eve:packages/eve/src/compiler/normalize-channel.ts`
- `vercel-eve:packages/eve/src/internal/authored-module-loader.ts`
- `vercel-eve:packages/eve/src/shared/agent-definition.ts`
- `vercel-eve:packages/eve/src/shared/sandbox-backend.ts`
- `vercel-eve:README.md`
- `withastro-flue:.agents/skills/channel-conformance/SKILL.md`
- `withastro-flue:AGENTS.md`
- `withastro-flue:apps/www/src/pages/start.md.ts`
- `withastro-flue:blueprints/channel--slack.md`
- `withastro-flue:blueprints/sandbox--modal.md`
- `withastro-flue:CHANGELOG.md`
- `withastro-flue:examples/hello-world/src/agents/session-test.ts`
- `withastro-flue:examples/sentry/src/workflows/hello.ts`
- `withastro-flue:package.json`
- `withastro-flue:packages/cli/package.json`
- `withastro-flue:packages/opentelemetry/package.json`
- `withastro-flue:packages/postgres/package.json`
- `withastro-flue:packages/runtime/package.json`
- `withastro-flue:packages/runtime/src/agent-definition.ts`
- `withastro-flue:packages/runtime/src/agent-execution-store.ts`
- `withastro-flue:packages/runtime/src/cloudflare/cf-sandbox.ts`
- `withastro-flue:packages/runtime/src/harness.ts`
- `withastro-flue:packages/runtime/src/index.ts`
- `withastro-flue:packages/runtime/src/node/local.ts`
- `withastro-flue:packages/runtime/src/runtime/dispatch-queue.ts`
- `withastro-flue:packages/runtime/src/runtime/dispatch.ts`
- `withastro-flue:packages/runtime/src/runtime/flue-app.ts`
- `withastro-flue:packages/runtime/src/runtime/handle-agent.ts`
- `withastro-flue:packages/runtime/src/runtime/run-store.ts`
- `withastro-flue:packages/runtime/src/sandbox.ts`
- `withastro-flue:packages/runtime/src/skill-frontmatter.ts`
- `withastro-flue:packages/runtime/src/sql-run-store.ts`
- `withastro-flue:packages/slack/src/index.ts`
- `withastro-flue:README.md`
---
## 01. Comparison Frame
> What Eve and Flue each optimize for, the shared problem space (durable agents with tools, skills, sandboxes, and channels), and the criteria—authoring ergonomics, runtime layering, deployment portability, and workflow boundaries—that make cross-repo lessons actionable.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/01-comparison-frame.md
- Generated: 2026-06-18T19:36:29.429Z
### Source Files
- `vercel-eve:README.md`
- `withastro-flue:README.md`
- `vercel-eve:docs/concepts/execution-model-and-durability.md`
- `withastro-flue:AGENTS.md`
- `vercel-eve:package.json`
- `withastro-flue:package.json`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/README.md](vercel-eve/README.md)
- [vercel-eve/AGENTS.md](vercel-eve/AGENTS.md)
- [vercel-eve/docs/introduction.md](vercel-eve/docs/introduction.md)
- [vercel-eve/docs/concepts/execution-model-and-durability.md](vercel-eve/docs/concepts/execution-model-and-durability.md)
- [vercel-eve/docs/reference/project-layout.md](vercel-eve/docs/reference/project-layout.md)
- [vercel-eve/packages/eve/README.md](vercel-eve/packages/eve/README.md)
- [vercel-eve/package.json](vercel-eve/package.json)
- [withastro-flue/README.md](withastro-flue/README.md)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/CHANGELOG.md](withastro-flue/CHANGELOG.md)
- [withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md](withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md)
- [withastro-flue/examples/hello-world/flue.config.ts](withastro-flue/examples/hello-world/flue.config.ts)
- [withastro-flue/package.json](withastro-flue/package.json)
</details>
# Comparison Frame
Eve and Flue both target the same product space: **durable agents** that combine model reasoning with **tools**, **skills**, **sandboxes**, and **channels** so autonomous work can run safely across many turns, transports, and restarts. They diverge sharply in what each optimizes for—Eve around filesystem-authored contracts and Vercel-native durability, Flue around a programmable TypeScript harness and portable deployment targets.
This page is a comparison brief, not a feature checklist. Use it to decide which ideas are portable when borrowing patterns across repositories, and which differences reflect intentional tradeoffs in authoring ergonomics, runtime layering, deployment portability, and workflow boundaries.
## Shared Problem Space
Both frameworks assume agents are long-lived programs, not one-shot API calls. A message arrives from some transport; the runtime assembles instructions, skills, tools, and history; work may call tools, delegate to subagents, pause for human input, and stream progress; state must survive long gaps and process restarts.
| Capability | Eve | Flue |
|------------|-----|------|
| Tools | Typed `defineTool` modules under `agent/tools/` | `defineTool` and MCP-connected tools composed in `createAgent` |
| Skills | Markdown procedures under `agent/skills/` | `SKILL.md` imported as `SkillReference` values |
| Sandboxes | Per-agent authored `agent/sandbox/` override | Virtual, local, or remote container sandboxes in agent config |
| Channels | `agent/channels/` for HTTP, Slack, Discord, etc. | Verified HTTP channels (Slack, Teams, GitHub, …) via blueprints |
| Subagents | Declared directories with isolated sandbox/skills/state | Named `defineAgentProfile` subagents and `task` delegation |
| Durability | Sessions durable by default via Workflow SDK | Agent session history + submission lifecycle; workflows handled separately |
Sources: [vercel-eve/README.md:23-29](), [vercel-eve/docs/introduction.md:30-34](), [withastro-flue/README.md:47-56](), [withastro-flue/AGENTS.md:7-17]()
## What Each Framework Optimizes For
### Eve: filesystem-first, Vercel-durable backend agents
Eve treats **the agent directory as the contract**. Instructions, skills, tools, connections, sandbox, channels, subagents, and schedules are files in named slots; Eve discovers them and compiles inspectable artifacts under `.eve/`. Identity is path-derived—there is no parallel registry to keep in sync.
```text
my-agent/agent/
├── agent.ts # additive runtime config (model, metadata, build)
├── instructions.md # always-on prompt
├── skills/ # optional procedures
├── tools/ # typed integrations
├── connections/ # MCP servers
├── sandbox/ # workspace override
├── channels/ # ingress + delivery
└── subagents/ # specialists
```
Eve prioritizes markdown-first authoring, durable message runs with explicit `continuationToken` and `sessionId` HTTP contracts, and a runtime that **hides workflow primitives** from authors while making every turn durable by default.
Sources: [vercel-eve/README.md:19-44](), [vercel-eve/docs/reference/project-layout.md:6-17](), [vercel-eve/package.json:5]()
### Flue: programmable harness, agent/workflow split, deploy-anywhere
Flue is framed as an **agent harness framework**, not a thin SDK wrapper. Authors compose agents in TypeScript with `createAgent`, explicitly wiring model, tools, skills, sandbox, and instructions. Projects compile into deployable server artifacts; build targets (Node, Cloudflare Workers, GitHub Actions, etc.) are selected at build time via `flue.config.ts`, while provider registration stays runtime-owned.
Flue draws a hard line between **continuing agents** (persistent sessions, direct prompts, `dispatch`) and **finite workflows** (`run(...)` with `runId`). Runs, `/runs`, and `flue logs` inspect workflows only—agent interactions correlate by instance, session, operation, and `dispatchId`.
Sources: [withastro-flue/README.md:3-41](), [withastro-flue/AGENTS.md:3-20](), [withastro-flue/CHANGELOG.md:230-239](), [withastro-flue/examples/hello-world/flue.config.ts:4-12]()
## Runtime Layering: Where Boundaries Live
The most actionable cross-repo lesson is **where each framework draws ownership lines** between transport, agent execution, and persistence.
### Eve: channel → harness → runtime
```mermaid
flowchart TB
subgraph transport["Ingress / delivery"]
CH["channels/<name>.ts"]
end
subgraph unit["One AI unit"]
HAR["harness"]
end
subgraph persist["Durability + orchestration"]
RT["runtime"]
WF["Workflow SDK primitives"]
end
CH -->|"normalizes message, owns continuationToken"| HAR
HAR -->|"{ session, next }"| RT
RT -->|"start(), resumeHook(), streams"| WF
RT -->|"owns sessionId for inspection"| CH
```
- **Channel** normalizes inbound transport, applies auth/delivery policy, owns `continuationToken`.
- **Harness** performs one unit of AI work.
- **Runtime** persists state, follows `next`, streams events, and owns workflow primitives authors never touch.
Sources: [vercel-eve/README.md:48-57](), [vercel-eve/packages/eve/README.md:148-157]()
### Flue: instance → harness → session → operation (workflows parallel)
```mermaid
flowchart TB
subgraph agents["Continuing agents"]
AM["agents/<name>.ts"]
AI["AgentInstance"]
H["Harness"]
S["Session"]
OP["Operation / Turn"]
AM --> AI --> H --> S --> OP
end
subgraph workflows["Finite jobs"]
WM["workflows/<name>.ts"]
RUN["run(...) / runId"]
WM --> RUN
end
subgraph shared["Shared capabilities"]
TOOLS["tools"]
SK["skills"]
SB["sandbox adapters"]
end
H --> TOOLS
H --> SK
H --> SB
RUN -->|"init(agent) when needed"| H
```
Flue's terminology stack—profile, created agent, module, instance, harness, session, operation, turn—is explicit in contributor docs. Workflows may initialize agents via `init(agent)` but remain a separate invocation surface with their own durability semantics.
Sources: [withastro-flue/AGENTS.md:7-20](), [withastro-flue/README.md:24-32]()
## Comparison Criteria
### 1. Authoring ergonomics
| Criterion | Eve | Flue |
|-----------|-----|------|
| Primary authoring surface | Filesystem slots under `agent/` | TypeScript modules (`agents/`, `workflows/`) |
| Instructions | `instructions.md` | Inline string or imported content in `createAgent` |
| Identity | Path-derived names; no `name` fields on `define*` | Agent instances have ids; harnesses and sessions have names |
| Discovery | Walk `agent/` tree; compiled `.eve/` artifacts | Vite build graph + CLI discovery (`flue.config.ts`) |
| Skill packaging | Drop markdown in `skills/` | Import `SKILL.md` with `{ type: 'skill' }`; bundle supporting files |
| Layout flexibility | Single recommended tree | `.flue/`, `src/`, or root `agents/` / `workflows/` |
**Portable lesson:** both treat skills as optional, load-on-demand expertise separate from always-on instructions. Eve optimizes for non-developer-readable trees; Flue optimizes for explicit composition and import graphs.
Sources: [vercel-eve/docs/introduction.md:38-61](), [vercel-eve/docs/reference/project-layout.md:8-17](), [withastro-flue/README.md:8-32](), [withastro-flue/AGENTS.md:34-35](), [withastro-flue/CHANGELOG.md:233]()
### 2. Runtime layering
| Criterion | Eve | Flue |
|-----------|-----|------|
| Transport ownership | Channel owns `continuationToken` | Channels + `route` / `websocket` middleware exports |
| Execution unit | Harness returns `{ session, next }` | Harness exposes sessions; operations are prompt/skill/task/shell |
| Hidden orchestration | Workflow SDK inside runtime only | Agent submission lifecycle + optional `PersistenceAdapter` |
| Public correlation IDs | `continuationToken` vs `sessionId` | Instance/session/operation/`dispatchId` vs workflow `runId` |
**Portable lesson:** separate **resume handles for the next user turn** from **inspection/streaming handles for observers**. Eve names this split directly in its HTTP protocol; Flue achieves a similar separation by refusing to call agent prompts "runs."
Sources: [vercel-eve/packages/eve/README.md:154-157](), [withastro-flue/AGENTS.md:20](), [withastro-flue/CHANGELOG.md:239]()
### 3. Deployment portability
| Criterion | Eve | Flue |
|-----------|-----|------|
| Primary deployment story | Durable backend agents on Vercel | Node, Cloudflare Workers, GitHub Actions, GitLab CI, Render, Daytona, … |
| Durability substrate | Workflow SDK (Vercel Workflow on Vercel) | Target-specific: DO SQLite on Cloudflare; optional `db.ts` adapters on Node |
| Build vs runtime config | `agent.ts` + compiled `.output/` | `flue.config.ts` (target, root, output) vs `app.ts` (providers) |
| Provider neutrality | Model string in `defineAgent`; connections for MCP | `registerProvider(...)` at runtime; BYOC API keys/bindings |
Eve's monorepo description and durability doc anchor it to Vercel's workflow stack. Flue documents materially different recovery behavior per target and requires explicit persistence for Node restarts.
Sources: [vercel-eve/package.json:5](), [vercel-eve/docs/concepts/execution-model-and-durability.md:16-22](), [withastro-flue/README.md:58-65](), [withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md:44-80](), [withastro-flue/examples/hello-world/flue.config.ts:4-12]()
### 4. Workflow boundaries
This is the sharpest conceptual fork.
| Aspect | Eve | Flue |
|--------|-----|------|
| What is a "run"? | Every **turn** is a durable workflow | **Runs** are workflow invocations only |
| Author workflow code? | No—primitives are implementation details | Yes—author `run(...)` for finite jobs |
| Workflow resumability | Steps checkpoint inside turns; parked work resumes | Workflows are **not resumable**; retry = new invocation |
| Message queue semantics | No durable FIFO per session; channel/app may queue | Per-instance ordered submission lifecycle on durable targets |
| When to use workflows | Implicit—durability wraps all agent turns | Explicit—bounded jobs (reports, CI tasks, batch orchestration) |
Eve parks turns durably when waiting on approval, OAuth, or subagents, then resumes from the workflow hook. Flue tells authors to move one-shot request/result modules into `workflows/` and keep long-lived agents in `agents/`.
```text
Eve turn lifecycle:
user message → durable workflow turn → step checkpoints → response
↓ (park)
resumeHook when input arrives
Flue dual lifecycle:
agent: input → session history → operation → later input continues
workflow: invoke run(runId) → result | error → retry = new runId
```
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:14-36](), [withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md:88-109](), [withastro-flue/CHANGELOG.md:230-239]()
## Durability Philosophy
Both frameworks checkpoint agent work conservatively and warn about non-idempotent side effects—but they apply durability at different granularities.
**Eve** nests session → turn → step. Steps are durable checkpoints inside a turn; completed steps never re-run. Sessions are durable by default with nothing to configure. Authors use `ctx.session` and `defineState` rather than workflow APIs.
**Flue** distinguishes durable **agents** (session history + submission reconciliation) from **workflows** (single-shot functions without arbitrary TypeScript checkpointing). Sandbox files and conversation history are intentionally separate concerns.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:8-24](), [withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md:82-86](), [withastro-flue/apps/docs/src/content/docs/concepts/durable-execution.md:88-93]()
## Making Cross-Repo Lessons Actionable
Use these decision lenses when borrowing patterns:
1. **Prefer filesystem slots** when many contributors edit instructions/skills/tools without touching shared TypeScript—Eve's model. **Prefer explicit `createAgent` composition** when agents are code-first products with importable skill bundles and multiple deploy targets—Flue's model.
2. **Hide orchestration** when every conversational turn should feel like durable backend logic with minimal author burden (Eve). **Expose workflows** when the product mixes open-ended agents with finite, inspectable jobs that need their own `runId` and logs (Flue).
3. **Treat channels as policy owners** for resume tokens and delivery shaping in both systems; do not assume either framework gives you a strict per-session chat FIFO without app-layer queuing (Eve states this explicitly).
4. **Keep sandbox lifecycle separate from session persistence** on both sides—durable conversation ≠ durable workspace files.
5. **Stay provider-neutral** for skills and tools: both support MCP and typed tools; model/provider choice remains an authored or runtime-registered concern, not a framework lock-in.
For Grok-Wiki or similar synthesis UIs, treat skill packs as **portable file or catalog sources** (repository trees, `SKILL.md`, `docs/`, blueprints)—not as dependencies on a single model vendor. The comparison frame above maps cleanly to wiki page types: **concept** (shared problem space), **architecture pattern** (runtime layering), **developer convention** (authoring layouts), and **failure mode** (workflow vs turn durability, message ordering, idempotent side effects).
## Summary
Eve optimizes for **inspectable, markdown-first agent directories** and **opinionated durability on Vercel** via a channel/harness/runtime split that keeps workflow machinery internal. Flue optimizes for a **composable TypeScript harness**, a **strict agent-vs-workflow boundary**, and **multi-target deployment** with explicit persistence and sandbox adapters. The shared substrate—tools, skills, sandboxes, channels, subagents, durable sessions—is real; the actionable differences are how you author (tree vs module), what counts as a run (every turn vs workflow-only), and how much orchestration the framework owns versus what your application must supply at deploy time.
---
## 02. Eve vs Flue Runtime Splits
> Contrasts Eve's three-way split—channel owns continuationToken, harness does one AI unit, runtime persists and streams—with Flue's harness → session → operation hierarchy and its explicit agents-versus-workflows boundary.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/02-eve-vs-flue-runtime-splits.md
- Generated: 2026-06-18T19:36:41.008Z
### Source Files
- `vercel-eve:README.md`
- `vercel-eve:docs/concepts/sessions-runs-and-streaming.md`
- `vercel-eve:packages/eve/src/shared/agent-definition.ts`
- `withastro-flue:AGENTS.md`
- `withastro-flue:packages/runtime/src/harness.ts`
- `withastro-flue:packages/runtime/src/runtime/flue-app.ts`
- `withastro-flue:packages/runtime/src/runtime/handle-agent.ts`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/README.md](vercel-eve/README.md)
- [vercel-eve/docs/concepts/sessions-runs-and-streaming.md](vercel-eve/docs/concepts/sessions-runs-and-streaming.md)
- [vercel-eve/docs/concepts/execution-model-and-durability.md](vercel-eve/docs/concepts/execution-model-and-durability.md)
- [vercel-eve/docs/channels/overview.mdx](vercel-eve/docs/channels/overview.mdx)
- [vercel-eve/packages/eve/src/harness/types.ts](vercel-eve/packages/eve/src/harness/types.ts)
- [vercel-eve/packages/eve/src/channel/types.ts](vercel-eve/packages/eve/src/channel/types.ts)
- [vercel-eve/packages/eve/src/execution/workflow-runtime.ts](vercel-eve/packages/eve/src/execution/workflow-runtime.ts)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/packages/runtime/src/harness.ts](withastro-flue/packages/runtime/src/harness.ts)
- [withastro-flue/packages/runtime/src/types.ts](withastro-flue/packages/runtime/src/types.ts)
- [withastro-flue/packages/runtime/src/runtime/flue-app.ts](withastro-flue/packages/runtime/src/runtime/flue-app.ts)
- [withastro-flue/packages/runtime/src/runtime/handle-agent.ts](withastro-flue/packages/runtime/src/runtime/handle-agent.ts)
- [withastro-flue/packages/runtime/src/client.ts](withastro-flue/packages/runtime/src/client.ts)
</details>
# Eve vs Flue Runtime Splits
Both Eve and Flue separate *ingress*, *AI execution*, and *persistence/streaming* — but they draw the boundaries differently. Eve uses a three-way split tuned for durable, channel-driven agents on Vercel: the channel owns conversation resumption, the harness performs one checkpointed AI unit, and the runtime drives workflow-backed durability and event streaming. Flue uses a developer-facing hierarchy — harness → session → operation — and draws a hard line between persistent agent sessions and ephemeral workflow runs.
Understanding these splits matters when porting patterns between frameworks, designing HTTP clients, or deciding where to put transport logic versus execution logic.
## Eve: Channel, Harness, Runtime
Eve's README states the mental model explicitly:
| Layer | Owns | Does not own |
|-------|------|--------------|
| **Channel** | Inbound transport normalization, auth, delivery policy, `continuationToken` | Model steps, workflow primitives, event stream storage |
| **Harness** | One unit of AI work; returns `{ session, next }` | Transport, workflow lifecycle, stream endpoints |
| **Runtime** | State persistence, following `next`, event streaming, workflow primitives | Channel-specific delivery semantics |
Sources: [vercel-eve/README.md:48-57](vercel-eve/README.md), [vercel-eve/docs/channels/overview.mdx:6-10](vercel-eve/docs/channels/overview.mdx)
### Two handles, two jobs
Eve's public HTTP contract deliberately splits resume from inspection:
- **`continuationToken`** — resume handle for the next user message. Owned by the channel. A stale token is rejected; only one active continuation exists per session.
- **`sessionId` / `runId`** — stream-and-inspect handle. Owned by the runtime. Used to attach to the NDJSON event stream and inspect durable history.
Sources: [vercel-eve/docs/concepts/sessions-runs-and-streaming.md:8-15](vercel-eve/docs/concepts/sessions-runs-and-streaming.md), [vercel-eve/packages/eve/src/channel/types.ts:296-339](vercel-eve/packages/eve/src/channel/types.ts)
This split lets a Slack channel mint transport-specific continuation tokens while the runtime exposes a stable `sessionId` for streaming regardless of which channel started the conversation.
### Harness: one step, one instruction
The harness boundary is defined in `StepResult`:
```135:138:vercel-eve/packages/eve/src/harness/types.ts
export interface StepResult {
readonly next: StepNext;
readonly session: HarnessSession;
}
```
`next` tells the runtime what to do after this step:
- A `StepFn` reference → call immediately (tool-loop continuation)
- `null` → park and wait for the next user message
- `StepDone` → conversation finished
`HarnessSession` is serializable plain data (history, tools schema, compaction config, `continuationToken`) — no resolved model instances or execute functions — so it survives workflow step checkpoints.
Sources: [vercel-eve/packages/eve/src/harness/types.ts:50-74](vercel-eve/packages/eve/src/harness/types.ts), [vercel-eve/packages/eve/src/harness/types.ts:123-144](vercel-eve/packages/eve/src/harness/types.ts)
### Runtime: workflow-backed durability
The runtime interface (`run`, `deliver`, `getEventStream`) is what routes and channels call. Internally it drives the Workflow SDK — `start`, `resumeHook`, `getRun` — via `workflow-runtime.ts`. Channels, tools, and hooks never touch workflow primitives directly; they interact only through the runtime surface.
Work nests as **session → turn → step**. Each turn is a durable workflow; steps checkpoint at model-call boundaries. Parked work (HITL approval, OAuth, subagent delegation) suspends the workflow until input arrives.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:8-28](vercel-eve/docs/concepts/execution-model-and-durability.md), [vercel-eve/packages/eve/src/execution/workflow-runtime.ts:1-26](vercel-eve/packages/eve/src/execution/workflow-runtime.ts)
```mermaid
flowchart TB
subgraph ingress ["Channel layer"]
CH["defineChannel / eve HTTP channel"]
CT["continuationToken"]
end
subgraph exec ["Harness layer"]
HS["HarnessSession (serializable)"]
ST["StepFn — one AI unit"]
NX["next: StepFn | null | StepDone"]
end
subgraph persist ["Runtime layer"]
RT["Runtime.run / deliver / getEventStream"]
WF["Workflow SDK (turnWorkflow)"]
ES["NDJSON event stream (sessionId)"]
end
CH -->|"normalize + auth"| RT
CT -->|"resume follow-up"| RT
RT -->|"invoke step"| ST
ST -->|"returns"| HS
ST -->|"returns"| NX
NX -->|"driver follows"| RT
RT --> WF
RT --> ES
```
## Flue: Harness → Session → Operation
Flue's terminology stack is documented in `AGENTS.md`:
```
AgentInstance (URL <id>)
└─ Harness — runtime-initialized agent environment
└─ Session — one harness.session(name?)
└─ Operation — one session.prompt / skill / task / shell call
└─ Turn — one LLM round-trip inside pi-agent-core
```
Sources: [withastro-flue/AGENTS.md:7-17](withastro-flue/AGENTS.md)
### Harness: session container, not AI step
`Harness` in `harness.ts` manages named sessions inside one agent instance. User code receives a `FlueSession` facade; the internal `Session` class (durable submission executor, abort/close, metadata) stays runtime-owned:
```84:87:withastro-flue/packages/runtime/src/harness.ts
// User code only ever receives the FlueSession facade; the internal
// Session (durable submission executor, abort/close, metadata) stays
// runtime-owned.
return createPublicSession(session);
```
A harness also exposes `sessions.get/create/delete`, `shell`, and `fs` outside any conversation transcript. Session operations for a given name are serialized through `runSessionOperation` to prevent concurrent open/load races.
Sources: [withastro-flue/packages/runtime/src/harness.ts:42-104](withastro-flue/packages/runtime/src/harness.ts)
### Session operations as the public execution API
`FlueSession` exposes four operation types, each returning a `CallHandle<T>`:
| Operation | Purpose |
|-----------|---------|
| `prompt(text)` | Model call with a text instruction |
| `skill(ref)` | Run a registered skill |
| `task(text)` | Delegate to a detached child session (subagent) |
| `shell(command)` | Shell command recorded in conversation state |
A **turn** is one LLM round-trip inside `pi-agent-core`, nested inside an operation. Operations can trigger multiple turns (tool loops, compaction, result-extraction retries).
Sources: [withastro-flue/packages/runtime/src/types.ts:535-604](withastro-flue/packages/runtime/src/types.ts)
### Agents vs workflows: explicit boundary
Flue draws a hard line that Eve does not mirror one-to-one:
> Runs are workflow-only. Direct HTTP/WebSocket agent prompts and dispatched agent inputs operate within persistent sessions and must not be described as runs.
| Concept | Identifier | HTTP route | Persistence model |
|---------|-----------|------------|-------------------|
| Agent session interaction | `submissionId` / instance `id` | `POST /agents/:name/:id` | Persistent session in harness |
| Async agent delivery | `dispatchId` | `dispatch()` API | Queued to existing session |
| Workflow execution | `runId` | `POST /workflows/:name` | One run per invocation; stream at `/runs/:runId` |
Agent prompts return 202 admission with stream coordinates (`streamUrl`, `offset`). Workflow runs get a fresh `runId` and a separate event stream. `dispatch()` admits input to a continuing session — it does not create workflow-run history.
Sources: [withastro-flue/AGENTS.md:20-21](withastro-flue/AGENTS.md), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:163-179](withastro-flue/packages/runtime/src/runtime/flue-app.ts), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:257-263](withastro-flue/packages/runtime/src/runtime/flue-app.ts), [withastro-flue/packages/runtime/src/runtime/handle-agent.ts:150-186](withastro-flue/packages/runtime/src/runtime/handle-agent.ts)
Workflows receive a `FlueContext` with `ctx.id === runId`. Agents are initialized inside that context via `ctx.init(agent)` when a workflow needs them, but direct agent HTTP traffic never creates a run record.
Sources: [withastro-flue/packages/runtime/src/client.ts:102-152](withastro-flue/packages/runtime/src/client.ts), [withastro-flue/packages/runtime/src/runtime/handle-agent.ts:189-216](withastro-flue/packages/runtime/src/runtime/handle-agent.ts)
```mermaid
flowchart TB
subgraph agentPath ["Agent path (persistent)"]
HTTP_A["POST /agents/:name/:id"]
ADM["admitAttachedSubmission"]
HAR["Harness"]
SES["FlueSession"]
OP["Operation: prompt | skill | task | shell"]
TURN["Turn (pi-agent-core)"]
DS_A["DS stream at agent URL"]
end
subgraph workflowPath ["Workflow path (ephemeral run)"]
HTTP_W["POST /workflows/:name"]
CTX["FlueContext (runId)"]
WH["workflow handler"]
DS_W["DS stream at /runs/:runId"]
end
HTTP_A --> ADM --> HAR --> SES --> OP --> TURN
ADM --> DS_A
HTTP_W --> CTX --> WH
WH -.->|"init(agent) when needed"| HAR
CTX --> DS_W
```
## Side-by-side comparison
| Dimension | Eve | Flue |
|-----------|-----|------|
| **Top-level split** | Channel / Harness / Runtime (3 layers) | Harness / Session / Operation (nested hierarchy) |
| **Ingress owner** | Channel (`defineChannel`, platform adapters) | HTTP routes + optional `channels/:name` handlers in `flue-app.ts` |
| **Resume handle** | `continuationToken` (channel-owned) | Persistent session keyed by `instanceId` + harness name + session name; no separate token type |
| **Stream handle** | `sessionId` (runtime-owned) | DS stream URL + offset at admission (agent URL or `/runs/:runId`) |
| **Smallest AI unit** | Harness step (`StepFn`) — internal framework boundary | Operation (`prompt`/`skill`/`task`/`shell`) — public developer API |
| **Durability engine** | Workflow SDK (`turnWorkflow`, `resumeHook`) | Session store + submission store + optional Durable Objects (Cloudflare) |
| **Workflow concept** | Every turn is a durable workflow; workflow primitives are runtime-internal | Workflows are a separate module type (`workflows/<name>.ts`); runs are workflow-only |
| **Subagent model** | Child durable session with own sandbox; parent gets `childSessionId` on stream | `session.task()` creates detached child session; history parent-owned until parent deleted |
| **Developer touches workflow code?** | No — runtime owns it | Yes — `export run(ctx)` in workflow modules |
## Where the designs converge
Both frameworks keep transport concerns out of the AI loop and make session state durable across process restarts. Both emit structured event streams for client attachment and replay. Both support compaction, subagent delegation, and provider-neutral model configuration (Eve via AI SDK `LanguageModel`; Flue via model specifier strings resolved at init).
The portable pattern is **split resume from observation**: Eve makes this explicit with two HTTP handles; Flue achieves a similar effect with admission receipts that carry `streamUrl` and `offset` separately from the conversational state key (`instanceId` + session name).
## Where they diverge — and what to borrow
**From Eve → Flue thinking:**
- Channel-owned continuation tokens decouple transport identity from runtime session identity. Useful when one agent serves Slack, HTTP, and webhooks with different resume semantics.
- Harness `next` as a declarative driver instruction (`continue | park | done`) keeps the execution loop testable without simulating HTTP.
**From Flue → Eve thinking:**
- Public operation types (`prompt`, `skill`, `task`, `shell`) give developers a stable, typed API surface rather than relying on implicit turn/step boundaries.
- Hard agents-vs-workflows separation prevents conflating persistent chat sessions with one-shot automation runs — a distinction Eve handles implicitly (everything is session-scoped) but Flue enforces in terminology, routes, and CLI (`flue logs` inspects workflow runs only).
## Summary
Eve optimizes for **filesystem-first, channel-driven agents** where the channel owns conversation resumption (`continuationToken`), the harness performs one serializable AI step returning `{ session, next }`, and the runtime wraps every turn in Workflow SDK durability while streaming on `sessionId`. Flue optimizes for **compiled deployable artifacts** with a developer-facing **harness → session → operation** hierarchy, using `pi-agent-core` turns inside explicit operations, and a strict boundary where workflow `runId`s and agent session interactions never share the same persistence or inspection path. Choosing between them is less about which split is "correct" and more about whether your primary abstraction is a durable channel conversation (Eve) or a typed operation inside a named session within an optional workflow run (Flue).
---
## 03. Eve Filesystem Slots & Compiled Artifacts
> How Eve treats the agent/ directory as the contract: path-derived identities for tools, skills, channels, and subagents; defineAgent for additive runtime config; and compilation into inspectable .eve/ runtime artifacts.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/03-eve-filesystem-slots-compiled-artifacts.md
- Generated: 2026-06-18T19:36:22.033Z
### Source Files
- `vercel-eve:docs/reference/project-layout.md`
- `vercel-eve:packages/eve/src/compiler/compile-agent.ts`
- `vercel-eve:packages/eve/src/compiler/normalize-channel.ts`
- `vercel-eve:packages/eve/src/internal/authored-module-loader.ts`
- `vercel-eve:apps/frameworks/nuxt/agent/agent.ts`
- `vercel-eve:apps/frameworks/nuxt/agent/instructions.md`
- `vercel-eve:docs/agent-config.md`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/docs/reference/project-layout.md](vercel-eve/docs/reference/project-layout.md)
- [vercel-eve/docs/agent-config.md](vercel-eve/docs/agent-config.md)
- [vercel-eve/docs/reference/cli.md](vercel-eve/docs/reference/cli.md)
- [vercel-eve/packages/eve/src/compiler/compile-agent.ts](vercel-eve/packages/eve/src/compiler/compile-agent.ts)
- [vercel-eve/packages/eve/src/compiler/artifacts.ts](vercel-eve/packages/eve/src/compiler/artifacts.ts)
- [vercel-eve/packages/eve/src/compiler/normalize-channel.ts](vercel-eve/packages/eve/src/compiler/normalize-channel.ts)
- [vercel-eve/packages/eve/src/compiler/normalize-tool.ts](vercel-eve/packages/eve/src/compiler/normalize-tool.ts)
- [vercel-eve/packages/eve/src/compiler/normalize-manifest.ts](vercel-eve/packages/eve/src/compiler/normalize-manifest.ts)
- [vercel-eve/packages/eve/src/compiler/normalize-agent-config.ts](vercel-eve/packages/eve/src/compiler/normalize-agent-config.ts)
- [vercel-eve/packages/eve/src/compiler/workspace-resources.ts](vercel-eve/packages/eve/src/compiler/workspace-resources.ts)
- [vercel-eve/packages/eve/src/internal/authored-module-loader.ts](vercel-eve/packages/eve/src/internal/authored-module-loader.ts)
- [vercel-eve/packages/eve/src/discover/discover-agent.ts](vercel-eve/packages/eve/src/discover/discover-agent.ts)
- [vercel-eve/packages/eve/src/discover/manifest.ts](vercel-eve/packages/eve/src/discover/manifest.ts)
- [vercel-eve/packages/eve/src/public/definitions/agent.ts](vercel-eve/packages/eve/src/public/definitions/agent.ts)
- [vercel-eve/apps/frameworks/nuxt/agent/agent.ts](vercel-eve/apps/frameworks/nuxt/agent/agent.ts)
- [vercel-eve/apps/frameworks/nuxt/agent/instructions.md](vercel-eve/apps/frameworks/nuxt/agent/instructions.md)
</details>
# Eve Filesystem Slots & Compiled Artifacts
Eve treats the `agent/` directory as a **filesystem contract**: where you place a file determines what it becomes at runtime. You do not declare identities inside `define*` helpers — the compiler derives tool, skill, channel, connection, and subagent names from paths. `agent.ts` is the one exception in spirit: it uses `defineAgent` for **additive** runtime configuration (model, compaction, build options) while identity still comes from the package or directory name.
That contract compiles into inspectable artifacts under `.eve/`. Discovery walks the tree without importing modules; compilation loads authored TypeScript, normalizes definitions, and writes JSON manifests plus a module map the runtime imports. Understanding this split — authored slots on disk versus compiled artifacts — is the fastest way to debug "why didn't Eve pick up my file?" and to see exactly what a deployment will load.
## The `agent/` directory as contract
Eve builds an agent by walking the filesystem under `agent/`. Each subdirectory is an **authored slot** with fixed semantics. Files outside recognized slots are ignored or diagnosed.
```text
my-agent/
├── package.json
├── agent/
│ ├── agent.ts ← runtime config (defineAgent)
│ ├── instructions.md ← system prompt (required at root)
│ ├── tools/ ← defineTool modules
│ ├── skills/ ← markdown, modules, or packages
│ ├── channels/ ← HTTP/messaging entrypoints (root only)
│ ├── connections/ ← MCP, OpenAPI, etc.
│ ├── hooks/ ← lifecycle subscribers
│ ├── schedules/ ← recurring jobs (root only)
│ ├── sandbox/ ← sandbox definition + workspace seeds
│ ├── lib/ ← import-only helpers (never mounted)
│ └── subagents/<id>/ ← nested agent packages
└── evals/ ← sibling of agent/, not inside it
```
Sources: [vercel-eve/docs/reference/project-layout.md:6-63]()
| Slot | Identity source | Subagents can author? | Notes |
|------|-----------------|----------------------|-------|
| `agent.ts` | Package name or app-root basename | Yes | `defineAgent`; optional at root (defaults apply) |
| `instructions.*` | Composed at build time | Optional | Required on root agent |
| `tools/*.ts` | Path under `tools/` | Yes | Nested dirs flatten to dashed slugs |
| `skills/*` | Path under `skills/` | Yes | Seeded to `/workspace/skills/...` |
| `channels/*` | Path under `channels/` | No | Root only; routes expand to entries |
| `connections/*.ts` | Filename | Yes | One connection per file |
| `subagents/<id>/` | Directory name `<id>` | Yes | Full nested slot surface |
| `lib/` | N/A (not registered) | Yes | Import-only; never reaches workspace |
Local subagents under `subagents/<id>/` mirror the root layout but **cannot** author `channels/` or `schedules/`. Each child discovers its own slots independently and inherits nothing from the parent.
Sources: [vercel-eve/docs/reference/project-layout.md:74-97]()
## Path-derived identity
**You never write a `name` or `id` field on a `define*` call.** The compiler strips extensions and slot prefixes to produce stable identities.
| Authored path | Resolves to |
|---------------|-------------|
| `agent/tools/get_weather.ts` | tool `get_weather` |
| `agent/tools/billing/refund.ts` | tool `billing-refund` (nested paths flatten) |
| `agent/skills/summarize.md` | skill `summarize` |
| `agent/connections/linear.ts` | connection `linear` |
| `agent/channels/slack.ts` | channel `slack` |
| `agent/subagents/researcher/agent.ts` | subagent `researcher` |
The root agent id comes from `package.json` `name` (scope stripped), falling back to the app-root directory basename. A local subagent id is its directory name under `subagents/`.
Sources: [vercel-eve/docs/reference/project-layout.md:8-19](), [vercel-eve/packages/eve/src/discover/manifest.ts:290-306](), [vercel-eve/packages/eve/src/compiler/normalize-tool.ts:27-51]()
Tools are special: path separators cannot reach model providers, so nested tool directories flatten to a single dashed segment. Authored `name` fields on tools are rejected by the normalizer.
```49:51:vercel-eve/packages/eve/src/compiler/normalize-tool.ts
const toolName = stripLogicalPathExtension(source.logicalPath)
.replace(/^tools\//, "")
.replaceAll("/", "-");
```
Channels derive the channel name from the filesystem path; each route in `defineChannel` becomes a separate compiled entry with its own URL path.
```36:61:vercel-eve/packages/eve/src/compiler/normalize-channel.ts
const channelName = stripLogicalPathExtension(source.logicalPath).replace(/^channels\//, "");
// ...
return definition.routes.map((route) => ({
kind: "channel" as const,
name: channelName,
logicalPath: source.logicalPath,
method: route.method.toUpperCase() as ChannelRouteMethod,
urlPath: route.path,
// ...
}));
```
## `defineAgent`: additive runtime config
`agent.ts` default-exports `defineAgent({ ... })` from `eve`. The helper is a typed identity function — it returns the definition unchanged while enforcing the `AgentDefinition` shape at compile time. It is **additive**: you configure runtime behavior; you do not set identity.
```33:37:vercel-eve/packages/eve/src/public/definitions/agent.ts
export function defineAgent<TAgent extends AgentDefinition>(
definition: ExactDefinition<TAgent, AgentDefinition>,
): TAgent {
return definition;
}
```
Typical fields include `model`, `modelOptions`, `compaction`, `experimental`, `outputSchema`, and `build.externalDependencies`. When `agent.ts` is absent at the root, Eve applies a default model; when present, `model` is required.
A minimal real example from the Nuxt framework demo:
```1:13:vercel-eve/apps/frameworks/nuxt/agent/agent.ts
import { defineAgent } from "eve";
export default defineAgent({
model: "anthropic/claude-opus-4.7",
modelOptions: {
providerOptions: {
anthropic: {
thinking: { type: "adaptive", display: "summarized" },
effort: "high",
},
},
},
});
```
Adjacent concerns stay in their slots — instructions in `instructions.md`, tools in `tools/`, sandbox in `sandbox/`, auth on channels — not in `defineAgent`.
Sources: [vercel-eve/docs/agent-config.md:6-68](), [vercel-eve/packages/eve/src/compiler/normalize-agent-config.ts:25-44]()
## Discovery and compilation pipeline
Eve separates **discovery** (filesystem classification, no module imports) from **compilation** (bundle, load, normalize). `compileAgent` orchestrates both and writes `.eve/` artifacts.
```mermaid
flowchart TB
subgraph authored ["Authored surface (agent/)"]
slots["Filesystem slots<br/>tools/, skills/, channels/, …"]
agentTs["agent.ts (defineAgent)"]
instr["instructions.md"]
end
subgraph discover ["Discovery (no imports)"]
walk["discoverAgent()<br/>walk + classify entries"]
srcManifest["agent-discovery-manifest.json"]
diag["diagnostics.json"]
end
subgraph compile ["Compilation (module load + normalize)"]
loader["authored-module-loader<br/>Rolldown bundle → .mjs cache"]
normalize["compileAgentManifest()<br/>per-slot normalizers"]
workspace["materializeWorkspaceResources()"]
outManifest["compiled-agent-manifest.json"]
moduleMap["module-map.mjs"]
end
subgraph runtime [".eve/ runtime load"]
eveStart["eve dev / eve start"]
end
slots --> walk
agentTs --> walk
instr --> walk
walk --> srcManifest
walk --> diag
srcManifest --> normalize
normalize --> loader
loader --> normalize
normalize --> workspace
workspace --> outManifest
normalize --> moduleMap
outManifest --> eveStart
moduleMap --> eveStart
```
### Phase 1: Discovery
`discoverAgent` reads sorted directory entries, classifies each against slot grammar, and builds an `AgentSourceManifest` listing source references (logical paths, source ids) plus diagnostics. It never imports authored modules — only filesystem structure matters.
```55:57:vercel-eve/packages/eve/src/discover/discover-agent.ts
/**
* Discovers the current agent's authored source graph without importing authored
* modules.
*/
```
Discovery walks each slot in sequence: instructions, `agent.ts`, channels, lib, schedules, connections, sandbox, tools, hooks, skills, and subagents.
Sources: [vercel-eve/packages/eve/src/discover/discover-agent.ts:58-196]()
### Phase 2: Compilation
`compileAgentManifest` loads each module-backed source through `loadModuleBackedDefinition`, which uses `authored-module-loader` to Rolldown-bundle TypeScript into content-hashed `.mjs` files under `node_modules/.cache/eve/authored-modules/`, then dynamically imports them. Concurrent loads of the same path are deduplicated to avoid bundle races.
Per-slot normalizers (`compileToolEntry`, `compileChannelDefinition`, `compileSkillSource`, etc.) validate exports against public Eve shapes and attach path-derived names. Workspace-bound resources (skills, sandbox seeds) are materialized under `.eve/compile/workspace-resources/`.
Sources: [vercel-eve/packages/eve/src/compiler/normalize-manifest.ts:31-53](), [vercel-eve/packages/eve/src/internal/authored-module-loader.ts:61-112](), [vercel-eve/packages/eve/src/compiler/workspace-resources.ts:17-29]()
### Phase 3: Artifact write
`writeCompilerArtifacts` persists everything under `.eve/`, even when discovery reports errors (so you can inspect partial output).
| Artifact | Purpose |
|----------|---------|
| `.eve/discovery/agent-discovery-manifest.json` | Raw discovery result — what Eve found on disk |
| `.eve/discovery/diagnostics.json` | Errors and warnings with source paths |
| `.eve/compile/compiled-agent-manifest.json` | Normalized surface the runtime loads |
| `.eve/compile/compile-metadata.json` | Schema version, generator info, content hashes, `ready`/`failed` status |
| `.eve/compile/module-map.mjs` | Flattened import map for authored module entrypoints |
| `.eve/compile/workspace-resources/` | Materialized skill and sandbox seed trees |
Sources: [vercel-eve/packages/eve/src/compiler/artifacts.ts:117-136](), [vercel-eve/docs/reference/cli.md:64-72]()
`compileAgent` throws `CompileAgentError` when diagnostics contain errors, but artifacts are already written — open `diagnostics.json` to see exactly which file violated the contract.
Sources: [vercel-eve/packages/eve/src/compiler/compile-agent.ts:58-85]()
## What reaches the runtime workspace
Eve does **not** mount the entire `agent/` tree into the sandbox. Only two authored sources land in the agent workspace at session bootstrap:
| Authored source | Runtime path |
|-----------------|--------------|
| `agent/skills/**` | `/workspace/skills/...` |
| `agent/sandbox/workspace/**` | `/workspace/...` |
Everything under `lib/` remains import-only source compiled into bundles; it never appears in the workspace. Instructions compose into the system prompt at build time rather than being mounted as files.
Sources: [vercel-eve/docs/reference/project-layout.md:65-72]()
## Debugging the contract
When a file is not discovered:
1. Run `eve info` — lists the active tools, skills, subagents, routes, and diagnostics.
2. Confirm the file sits in the correct slot per the table above.
3. Check root-vs-subagent boundaries (e.g. `channels/` and `schedules/` are root-only).
4. Open `.eve/discovery/diagnostics.json` for shape errors on `define*` exports.
Artifacts are preserved on partial failure, so a failed `eve build` still leaves a inspectable snapshot of what discovery found and where normalization broke.
Sources: [vercel-eve/docs/reference/project-layout.md:114-116](), [vercel-eve/docs/reference/cli.md:62-72]()
## Summary
Eve's agent authoring model is **convention over configuration**: the `agent/` directory layout is the contract, path-derived identities replace inline `name` fields, and `defineAgent` adds runtime config without changing identity. Discovery classifies the filesystem; compilation bundles and normalizes modules into `.eve/` artifacts that the runtime imports verbatim. Treat `.eve/` as the ground truth for what a running agent actually loads — especially when debugging discovery misses or comparing local builds to deployments.
---
## 04. Flue createAgent & Agent/Workflow Split
> How Flue authors agents as TypeScript modules with createAgent, defineAgentProfile, and explicit skill/tool imports; how workflows export run(ctx) for finite executions; and how the CLI build graph discovers agents/ and workflows/ layouts.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/04-flue-createagent-agent-workflow-split.md
- Generated: 2026-06-18T19:36:29.827Z
### Source Files
- `withastro-flue:packages/runtime/src/agent-definition.ts`
- `withastro-flue:packages/runtime/src/index.ts`
- `withastro-flue:examples/hello-world/src/agents/session-test.ts`
- `withastro-flue:examples/sentry/src/workflows/hello.ts`
- `withastro-flue:CHANGELOG.md`
- `withastro-flue:apps/www/src/pages/start.md.ts`
- `withastro-flue:packages/cli/package.json`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [withastro-flue/packages/runtime/src/agent-definition.ts](withastro-flue/packages/runtime/src/agent-definition.ts)
- [withastro-flue/packages/runtime/src/index.ts](withastro-flue/packages/runtime/src/index.ts)
- [withastro-flue/packages/runtime/src/types.ts](withastro-flue/packages/runtime/src/types.ts)
- [withastro-flue/packages/cli/src/lib/build.ts](withastro-flue/packages/cli/src/lib/build.ts)
- [withastro-flue/packages/cli/src/lib/source-root.ts](withastro-flue/packages/cli/src/lib/source-root.ts)
- [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts](withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts)
- [withastro-flue/packages/cli/src/lib/build-plugin-node.ts](withastro-flue/packages/cli/src/lib/build-plugin-node.ts)
- [withastro-flue/packages/cli/src/lib/config.ts](withastro-flue/packages/cli/src/lib/config.ts)
- [withastro-flue/examples/hello-world/src/agents/session-test.ts](withastro-flue/examples/hello-world/src/agents/session-test.ts)
- [withastro-flue/examples/hello-world/src/workflows/hello.ts](withastro-flue/examples/hello-world/src/workflows/hello.ts)
- [withastro-flue/examples/sentry/src/workflows/hello.ts](withastro-flue/examples/sentry/src/workflows/hello.ts)
- [withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts](withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts)
- [withastro-flue/examples/chat-sdk/src/agents/assistant.ts](withastro-flue/examples/chat-sdk/src/agents/assistant.ts)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/CHANGELOG.md](withastro-flue/CHANGELOG.md)
- [withastro-flue/apps/www/src/pages/start.md.ts](withastro-flue/apps/www/src/pages/start.md.ts)
</details>
# Flue createAgent & Agent/Workflow Split
Flue separates **persistent agents** from **finite workflows** at the file-system and runtime level. Agents are TypeScript modules that default-export a `createAgent(...)` initializer; workflows are separate modules that export a `run(ctx)` function for bounded jobs. The CLI discovers both layouts, feeds them through a shared Vite build graph, and generates a server entry that wires addressable agents and invocable workflows into one deployable artifact.
This split matters because the two shapes solve different problems: agents keep sessions across direct HTTP prompts, WebSocket conversations, and `dispatch(...)` deliveries, while workflows own persisted **runs** with a start, a result, and an event stream. Mixing them — for example, putting one-shot jobs in `agents/` — breaks discovery, routing, and run semantics.
## Agents vs workflows
| Dimension | Agent module (`agents/<name>.ts`) | Workflow module (`workflows/<name>.ts`) |
|-----------|-----------------------------------|------------------------------------------|
| Export contract | Default-export `createAgent(...)` | Export callable `run(ctx)` |
| Identity | Addressed by module basename (`session-test` → `/agents/session-test/:id`) | Addressed by workflow name; each invocation gets a `run_<ulid>` |
| Lifetime | Persistent instance with harnesses and sessions | Finite execution; `ctx.id` is the run id |
| Typical use | Chat assistants, support bots, event-driven triage | Summarize a ticket, generate a report, CI/batch jobs |
| Agent initialization | Runtime calls `initialize` on direct interaction or dispatch | Workflow calls `ctx.init(createdAgent)` when it needs a harness |
| Optional HTTP surface | `export const route` middleware | `export const route` middleware |
Sources: [withastro-flue/AGENTS.md:7-18](), [withastro-flue/CHANGELOG.md:230-238]()
The terminology stack is explicit in project docs: a **profile** (`defineAgentProfile`) is reusable configuration; a **created agent** (`createAgent`) is the runtime initializer; an **agent module** default-exports that created agent; workflows initialize created agents locally via `init(agent)` rather than re-declaring inline config.
```text
agents/<name>.ts workflows/<name>.ts
│ │
▼ ▼
default-export export async function run(ctx)
createAgent(...) │
│ ctx.init(agent) ──► FlueHarness
│ │ │
▼ ▼ ▼
addressable instance finite run session.prompt/skill/task
(sessions persist) (result + stream)
```
## Authoring agents with `createAgent` and `defineAgentProfile`
### `createAgent` — the module contract
`createAgent` wraps an initializer function and returns a frozen `CreatedAgent` marker object (`__flueCreatedAgent: true`). The initializer runs whenever the runtime prepares a harness — both for addressable agent interactions and when a workflow calls `ctx.init()`. It is **not** a one-time constructor for a persistent instance id.
```typescript
// withastro-flue/examples/hello-world/src/agents/session-test.ts
const sessionTest = defineAgentProfile({
instructions: 'You are a test agent for session-oriented message delivery.',
});
export default createAgent(() => ({ profile: sessionTest }));
```
Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:60-80](), [withastro-flue/examples/hello-world/src/agents/session-test.ts:1-9]()
Key rules enforced at authoring time:
- Agent modules **must** default-export `createAgent(...)`; the build normalizer rejects anything else.
- The initializer must return an `AgentRuntimeConfig` with known fields (`model`, `instructions`, `profile`, `skills`, `tools`, `subagents`, `sandbox`, etc.).
- Agent names come from the **module filename**, not a top-level `name` on the runtime config (`name` is profile-only for subagents).
### `defineAgentProfile` — reusable baselines
`defineAgentProfile` validates and returns a reusable `AgentProfile` object. Use it to share instructions, model defaults, skills, tools, and subagents across a created agent or as named subagents for `session.task()`.
Validation is strict: unknown fields, duplicate capability names, invalid thinking levels, and circular subagent graphs all throw at definition time via Valibot schemas.
Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:48-57](), [withastro-flue/packages/runtime/src/agent-definition.ts:21-38]()
### Explicit skill and tool imports
Flue does not auto-discover arbitrary npm skills at agent definition time for statically bundled capabilities. Authors import skills and tools explicitly:
| Capability | Import / declaration | Where it attaches |
|------------|---------------------|-------------------|
| Packaged skill | `import review from '../skills/review/SKILL.md' with { type: 'skill' }` | `createAgent(() => ({ skills: [review] }))` |
| Custom tool | `defineTool({ name, description, parameters, execute })` | Agent config `tools: [...]` or `session.prompt(..., { tools })` |
| Named subagent | `defineAgentProfile({ name: 'greeter', ... })` | `subagents: [greeter]` on agent config |
| Runtime workspace skill | `AGENTS.md` / `.agents/skills/` | Discovered from session cwd at runtime (not build graph) |
Packaged `SKILL.md` imports flow through the Vite graph and land in `virtual:flue/packaged-skills` at build time. Runtime workspace skills are a separate path — the build comment in `build.ts` notes they are discovered from session cwd, not from static module imports.
```typescript
// withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts
import review from '../skills/review/SKILL.md' with { type: 'skill' };
const agent = createAgent(() => ({ model: 'anthropic/claude-haiku-4-5', skills: [review] }));
```
```typescript
// withastro-flue/examples/chat-sdk/src/agents/assistant.ts (tools on agent config)
tools: [
defineTool({
name: 'reply_to_chat_thread',
description: 'Post a response into the originating Chat SDK thread.',
parameters: v.object({ threadId: v.string(), text: v.string() }),
execute: async ({ threadId, text }) => { /* ... */ },
}),
],
```
Sources: [withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts:1-13](), [withastro-flue/examples/chat-sdk/src/agents/assistant.ts:41-54](), [withastro-flue/packages/cli/src/lib/build.ts:36-37](), [withastro-flue/packages/cli/src/lib/build-plugin-node.ts:69-70]()
`defineTool` parameters use Valibot schemas (not TypeBox); `defineAgentProfile` and `createAgent` both validate tool definitions require `name`, `description`, `parameters`, and `execute`.
Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:234-251](), [withastro-flue/CHANGELOG.md:17-18]()
## Authoring workflows with `run(ctx)`
Workflow modules export an async `run` function receiving `FlueContext`. The context supplies the run id, typed payload, environment bindings, optional HTTP `req`, structured `log`, and `init(agent)` for harness creation.
### Finite execution pattern
Workflows return a result when `run` completes. They do not default-export anything — the build normalizer requires `typeof mod.run === 'function'`.
```typescript
// withastro-flue/examples/sentry/src/workflows/hello.ts
export async function run(ctx: FlueContext) {
ctx.log.info('hello workflow starting', { instanceId: ctx.id });
return { greeting: 'hello from flue', id: ctx.id };
}
```
A workflow that needs model calls initializes a **local** created agent (which may be defined inline in the same file or imported from an agent module):
```typescript
// withastro-flue/examples/hello-world/src/workflows/hello.ts
const agent = createAgent(() => ({ model: 'anthropic/claude-sonnet-4-6' }));
export async function run({ init, log }: FlueContext) {
const harness = await init(agent);
const session = await harness.session();
const response = await session.prompt('What is 2 + 2? Return only the number.', {
result: v.object({ answer: v.number() }),
});
return response.data;
}
```
Sources: [withastro-flue/examples/sentry/src/workflows/hello.ts:22-28](), [withastro-flue/examples/hello-world/src/workflows/hello.ts:6-34](), [withastro-flue/packages/runtime/src/types.ts:431-466]()
### When to add a workflow
Project scaffolding guidance recommends **agent only** for continuing assistants and **agent + workflow** only when a bounded, result-oriented job is needed. Workflows are not required to test agents — `flue connect <agent-name> local` suffices for local interaction.
Sources: [withastro-flue/apps/www/src/pages/start.md.ts:18-37](), [withastro-flue/apps/www/src/pages/start.md.ts:79-83]()
Optional `export const route: WorkflowRouteHandler` (or `AgentRouteHandler` on agents) declares HTTP middleware; without it, the module is still discovered but lacks a direct HTTP transport in the manifest.
Sources: [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:48-56]()
## CLI build graph: discovering `agents/` and `workflows/`
### Source root resolution
The CLI picks exactly one source directory and never mixes layouts:
1. `<root>/.flue/` if it exists as a directory
2. else `<root>/src/` if it exists
3. else `<root>` (root layout)
Within the chosen `sourceRoot`, modules live in immediate child folders `agents/`, `workflows/`, and `channels/`.
```typescript
// withastro-flue/packages/cli/src/lib/source-root.ts
for (const sourceDirectory of ['.flue', 'src']) {
const candidate = path.join(root, sourceDirectory);
if (fs.statSync(candidate).isDirectory()) return candidate;
}
return root;
```
Sources: [withastro-flue/packages/cli/src/lib/source-root.ts:4-11](), [withastro-flue/packages/cli/src/lib/config.ts:37-39](), [withastro-flue/apps/www/src/pages/start.md.ts:39-45]()
### Module discovery
`discoverAgents` and `discoverWorkflows` scan `sourceRoot/agents/` and `sourceRoot/workflows/` respectively:
- Accept `.ts`, `.js`, `.mts`, `.mjs` files; skip `.d.ts` / `.d.mts` declaration files
- Derive the module **name** from the basename (e.g. `hello.ts` → `hello`)
- Reject duplicate basenames and invalid names (`:` forbidden in agent/channel names)
- Require at least one agent or workflow file; otherwise build throws
Sources: [withastro-flue/packages/cli/src/lib/build.ts:62-67](), [withastro-flue/packages/cli/src/lib/build.ts:98-103](), [withastro-flue/packages/cli/src/lib/build.ts:287-316]()
### Generated entry and Vite graph
The build pipeline:
1. Resolves `sourceRoot` from project `root`
2. Discovers agents, workflows, channels, and optional `app.ts` / `db.ts` / `cloudflare.ts` entries
3. Delegates to a target plugin (`NodePlugin` or `CloudflarePlugin`) to `generateEntryPoint`
4. Emits imports for every discovered module into `<root>/.flue-vite/_entry.ts`
5. Calls `normalizeBuiltModules` to validate exports and build the runtime manifest
6. Bundles through Vite (packaging statically imported skills via `virtual:flue/packaged-skills`)
```mermaid
flowchart TB
subgraph Authoring["Authoring (one sourceRoot)"]
AR[".flue/ or src/ or root"]
A["agents/*.ts<br/>default-export createAgent"]
W["workflows/*.ts<br/>export run(ctx)"]
AR --> A
AR --> W
end
subgraph CLI["@flue/cli build"]
SR["resolveSourceRoot(root)"]
DA["discoverAgents(sourceRoot)"]
DW["discoverWorkflows(sourceRoot)"]
GE["generateEntryPoint → .flue-vite/_entry.ts"]
NM["normalizeBuiltModules()"]
VT["Vite bundle → dist/"]
SR --> DA
SR --> DW
DA --> GE
DW --> GE
GE --> NM
NM --> VT
end
subgraph Runtime["@flue/runtime server"]
CA["createdAgents[name]"]
WH["workflowHandlers[name]"]
MF["manifest.agents / manifest.workflows"]
NM --> CA
NM --> WH
NM --> MF
end
A --> DA
W --> DW
```
Sources: [withastro-flue/packages/cli/src/lib/build-plugin-node.ts:14-112](), [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:21-57]()
### Normalization contract (build-time enforcement)
| Module kind | Required export | Optional exports | Build-time error if violated |
|-------------|----------------|------------------|------------------------------|
| Agent | `default`: `createAgent(...)` with `__flueCreatedAgent` | `route`, `description` | Missing default; duplicate `createAgent` value across modules |
| Workflow | `run` function | `route` | `run` not callable |
| Channel | named `channel` binding with routes | — | (separate discovery path) |
The normalizer registers `createdAgents[name] = mod.default` for dispatchable agents and `localWorkflowHandlers[name] = mod.run` for all workflows. HTTP-exposed workflows additionally populate `workflowHandlers` when `route` is exported.
Sources: [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:32-57]()
### Dev server reload scope
The dev watcher treats changes under `agents/`, `workflows/`, and related source paths as rebuild triggers, keeping the generated entry in sync with the discovered module set.
Sources: [withastro-flue/packages/cli/src/lib/dev.ts:628-629]()
## Runtime initialization: two paths, one `createAgent`
```mermaid
sequenceDiagram
participant AM as agents/hello.ts
participant WM as workflows/job.ts
participant RT as @flue/runtime
participant CA as createAgent initializer
Note over AM,RT: Addressable agent path
RT->>AM: import default created agent
RT->>CA: initialize({ id, env, payload: undefined })
CA-->>RT: AgentRuntimeConfig
RT->>RT: harness.session() → persistent sessions
Note over WM,RT: Workflow path
RT->>WM: invoke run(ctx)
WM->>RT: ctx.init(createdAgent)
RT->>CA: initialize({ id: runId, env, payload })
CA-->>RT: AgentRuntimeConfig
RT-->>WM: FlueHarness
WM->>WM: session operations → return result
```
Both paths call the same `initialize` function on the `CreatedAgent`. The difference is **who owns the lifecycle**: the runtime owns addressable agent instances; the workflow owns the run and decides when to call `init`.
Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:65-70](), [withastro-flue/packages/runtime/src/types.ts:60-67](), [withastro-flue/packages/runtime/src/types.ts:462-466]()
## Practical authoring checklist
1. Pick one source layout (`.flue`, `src`, or root) and place modules only there.
2. Create `agents/<name>.ts` with `export default createAgent(() => ({ ... }))`.
3. Extract reusable config into `defineAgentProfile` when sharing across agents or subagents.
4. Import skills (`SKILL.md` with `{ type: 'skill' }`) and tools (`defineTool`) explicitly in the initializer or per-session `prompt` options.
5. Add `workflows/<name>.ts` with `export async function run(ctx)` only for bounded jobs; call `await ctx.init(agent)` inside.
6. Run `flue build` — the CLI lists discovered agent and workflow names before bundling.
7. Test agents locally with `flue connect`; invoke workflows via `POST /workflows/<name>`.
Sources: [withastro-flue/apps/www/src/pages/start.md.ts:71-103](), [withastro-flue/packages/cli/src/lib/build.ts:83-94]()
## Summary
Flue's agent/workflow split is enforced at three layers: **file layout** (`agents/` vs `workflows/`), **export contracts** (`default createAgent` vs `export run`), and **runtime semantics** (persistent instances and sessions vs finite runs with results). `createAgent` and `defineAgentProfile` are the composable authoring primitives; skills and tools are wired through explicit imports and validated definitions. The `@flue/cli` build discovers modules from a single resolved source root, generates a normalized entry point, and bundles everything through Vite into a deployable server that exposes both addressable agents and invocable workflows from one manifest.
---
## 05. Durable Execution & Checkpointing
> Compares Eve's workflow-backed session/turn/step model with automatic step checkpoints and parked work, against Flue's pluggable run and agent-execution stores, dispatch queue, and workflow run IDs exposed over HTTP.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/05-durable-execution-checkpointing.md
- Generated: 2026-06-18T19:37:37.352Z
### Source Files
- `vercel-eve:docs/concepts/execution-model-and-durability.md`
- `vercel-eve:docs/concepts/default-harness.md`
- `vercel-eve:packages/eve/src/client/session.ts`
- `withastro-flue:packages/runtime/src/runtime/run-store.ts`
- `withastro-flue:packages/runtime/src/sql-run-store.ts`
- `withastro-flue:packages/runtime/src/agent-execution-store.ts`
- `withastro-flue:packages/runtime/src/runtime/dispatch-queue.ts`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/docs/concepts/execution-model-and-durability.md](vercel-eve/docs/concepts/execution-model-and-durability.md)
- [vercel-eve/docs/concepts/default-harness.md](vercel-eve/docs/concepts/default-harness.md)
- [vercel-eve/docs/concepts/sessions-runs-and-streaming.md](vercel-eve/docs/concepts/sessions-runs-and-streaming.md)
- [vercel-eve/packages/eve/src/client/session.ts](vercel-eve/packages/eve/src/client/session.ts)
- [vercel-eve/packages/eve/src/execution/workflow-runtime.ts](vercel-eve/packages/eve/src/execution/workflow-runtime.ts)
- [vercel-eve/packages/eve/src/execution/turn-workflow.ts](vercel-eve/packages/eve/src/execution/turn-workflow.ts)
- [vercel-eve/packages/eve/src/execution/durable-session-store.ts](vercel-eve/packages/eve/src/execution/durable-session-store.ts)
- [vercel-eve/packages/eve/src/execution/subagent-adapter.ts](vercel-eve/packages/eve/src/execution/subagent-adapter.ts)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/packages/runtime/src/runtime/run-store.ts](withastro-flue/packages/runtime/src/runtime/run-store.ts)
- [withastro-flue/packages/runtime/src/sql-run-store.ts](withastro-flue/packages/runtime/src/sql-run-store.ts)
- [withastro-flue/packages/runtime/src/agent-execution-store.ts](withastro-flue/packages/runtime/src/agent-execution-store.ts)
- [withastro-flue/packages/runtime/src/runtime/dispatch-queue.ts](withastro-flue/packages/runtime/src/runtime/dispatch-queue.ts)
- [withastro-flue/packages/runtime/src/node/agent-coordinator.ts](withastro-flue/packages/runtime/src/node/agent-coordinator.ts)
- [withastro-flue/packages/runtime/src/runtime/flue-app.ts](withastro-flue/packages/runtime/src/runtime/flue-app.ts)
</details>
# Durable Execution & Checkpointing
Both Eve and Flue solve the same underlying problem — agent work must survive crashes, redeploys, and long waits for human input — but they choose different durability boundaries and storage models. Eve wraps the agent loop in a managed Workflow SDK execution graph where every model step is an automatic checkpoint. Flue splits agent sessions from workflow runs and exposes pluggable stores, a dispatch admission queue, and HTTP-addressable run IDs that clients can inspect independently of the agent conversation model.
Understanding where each framework checkpoints, what it replays on recovery, and how clients resume work is the key to integrating either system safely.
## Conceptual split: one durable conversation vs. layered stores
| Dimension | Eve (`vercel-eve`) | Flue (`withastro-flue`) |
|-----------|-------------------|-------------------------|
| Primary durable unit | **Session** — a long-lived conversation spanning days | **Agent session** (persistent harness state) and **workflow run** (finite job) are separate |
| Checkpoint grain | **Step** — one model call plus its tool calls | **Turn journal phase** inside a **submission attempt**; workflow runs tracked as `RunRecord` |
| Execution engine | Workflow SDK (`start`, `resumeHook`, `"use step"`) owned by the runtime | Coordinator + `AgentSubmissionStore` leases; workflow modules get their own `runId` |
| Resume handle | `continuationToken` (park/resume) + `sessionId` (stream) | `dispatchId` for dispatched agent input; opaque `run_<ulid>` for workflow runs |
| Message ordering | No durable per-session FIFO; client should serialize sends | Per-session submission queue: at most one runnable head per session |
| Storage model | Framework-owned workflow persistence | Pluggable `PersistenceAdapter` (`RunStore`, `AgentExecutionStore`, `EventStreamStore`) |
| HTTP inspection | `GET /eve/v1/session/<sessionId>/stream` (NDJSON) | `GET /runs/:runId` (Durable Streams); `GET /runs/:runId?meta` (JSON run record) |
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:9-36](), [withastro-flue/AGENTS.md:7-20](), [withastro-flue/packages/runtime/src/agent-execution-store.ts:336-353]()
## Eve: workflow-backed session / turn / step model
### Three nesting levels
Eve organizes durable work as:
1. **Session** — the whole conversation or task; survives process restarts and redeploys without configuration.
2. **Turn** — one user message and all work it triggers until the agent responds.
3. **Step** — a durable checkpoint inside a turn (one model call and the tool calls it makes).
Every turn runs as a durable workflow. The runtime checkpoints progress and serializes durable state at each step boundary. Agent code inside tools and the sandbox feels synchronous even though the session underneath is durable.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:9-16]()
### Runtime architecture
Eve's workflow runtime uses a long-lived **driver workflow** that owns the event stream and dispatches each turn as a **child workflow run**:
```text
Client Eve runtime Workflow SDK
| | |
|-- POST /session ---------->| start(workflowEntry) ------------->| driver run (pinned deployment)
|<-- sessionId, token ------| |
|-- GET .../stream --------->| event stream (NDJSON) |
| | start(turnWorkflow) -------------->| child turn run (latest deployment)
| | turnStep() -> "use step" ------->| checkpoint boundary
| | park -> resumeHook(token) ----->| suspend (no compute)
|-- POST + continuationToken>| resumeHook ----------------------->| resume parked turn
```
The driver is pinned to the deployment that called `start()`, while child turn workflows route to the latest deployment. Session snapshots travel inside workflow step results as the atomic persistence boundary for program memory.
Sources: [vercel-eve/packages/eve/src/execution/workflow-runtime.ts:74-98](), [vercel-eve/packages/eve/src/execution/turn-workflow.ts:24-40](), [vercel-eve/packages/eve/src/execution/durable-session-store.ts:1-16]()
### Checkpoint and replay semantics
On crash, timeout, or redeploy mid-turn:
- **Completed steps never re-run** — Eve replays the recorded result.
- **A step interrupted mid-execution re-runs** — non-idempotent side effects (charges, emails) need idempotency keys or approval gates.
There is nothing to configure; Eve owns the workflow lifecycle and sessions are durable by default. Workflow primitives (`start()`, `resumeHook()`, etc.) are implementation details; channels, tools, and hooks never touch them directly.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:18-24](), [vercel-eve/packages/eve/src/execution/subagent-adapter.ts:100-112]()
### Parked work
Some work must wait: human tool approval, `ask_question`, OAuth sign-in, or a long-running subagent. At those points the turn **parks durably**. The workflow suspends and holds no compute until the awaited input arrives. When it does, the conversation resumes exactly where it left off.
Parked states surface on the event stream as `input.requested`, `authorization.required`, or `session.waiting`. Clients resume by POSTing to the session endpoint with the current `continuationToken`; a stale token is rejected.
OAuth and terminal callbacks use unguessable workflow hook tokens on framework-owned routes (`/eve/v1/callback/:token`, `/eve/v1/connections/:name/callback/:token`) that call `resumeHook(token, payload)`.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:26-28](), [vercel-eve/docs/concepts/sessions-runs-and-streaming.md:8-15,45-61](), [vercel-eve/packages/eve/src/protocol/routes.ts:77-99]()
### Client session state
The Eve client tracks `continuationToken`, `sessionId`, and `streamIndex` across `send()` calls. Serialize `ClientSession.state` to persist and resume later. HITL input responses trigger up to 10 delivery retries when the target session is temporarily not found.
Sources: [vercel-eve/packages/eve/src/client/session.ts:30-53,262-296]()
### What Eve does not guarantee
Eve does **not** maintain a durable FIFO queue of user messages per session. The `continuationToken` is a resume handle for the session's current workflow hook, not a general message-queue address. For deterministic behavior, send one user turn at a time and wait for `session.waiting` before sending the next message. Channels that receive bursts should keep their own per-session queue.
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:30-36]()
## Flue: pluggable stores, dispatch queue, and workflow run IDs
### Terminology: runs are workflow-only
Flue distinguishes agent conversations from workflow jobs:
- **Agent path**: persistent instances, harnesses, sessions, operations, and turns. Direct prompts and `dispatch()` inputs live here; they correlate by `dispatchId`, not `runId`.
- **Workflow path**: `workflows/<name>.ts` exports `run(...)`; each invocation gets a unique `ctx.id === runId`.
`GET /runs/:runId` and `flue logs` inspect **workflow runs only**.
Sources: [withastro-flue/AGENTS.md:7-20]()
### Persistence adapter bundle
Users configure durability by exporting a `PersistenceAdapter` from `db.ts`. At startup the framework calls `migrate()` (if present), then `connect()` to obtain three stores:
| Store | Responsibility |
|-------|----------------|
| `executionStore.sessions` | Agent session snapshots |
| `executionStore.submissions` | Durable submission lifecycle, turn journals, stream chunks, leases |
| `runStore` | Workflow run records (`active` / `completed` / `errored`) |
| `eventStreamStore` | Append-only durable event streams for agents and runs |
Adapters exist for SQLite, Postgres, MySQL, Redis, MongoDB, and Cloudflare Durable Object SQLite. Schema versioning is enforced at boot — an unknown or newer stored version fails loudly before any read/write.
Sources: [withastro-flue/packages/runtime/src/agent-execution-store.ts:336-397](), [withastro-flue/packages/runtime/src/sql-run-store.ts:1-8,121-143]()
### RunStore: workflow run lifecycle
`RunStore` persists one record per workflow run:
```typescript
// withastro-flue/packages/runtime/src/runtime/run-store.ts
export type RunStatus = 'active' | 'completed' | 'errored';
export interface RunStore {
createRun(input: CreateRunInput): Promise<void>; // idempotent, first-writer-wins
endRun(input: EndRunInput): Promise<void>;
getRun(runId: string): Promise<RunRecord | null>;
lookupRun(runId: string): Promise<RunPointer | null>;
listRuns(opts?: ListRunsOpts): Promise<ListRunsResponse>;
}
```
`createRun` uses `INSERT OR IGNORE` semantics so a replayed `runId` never resurrects a terminal record back to `active`. The SQL adapter stores rows in `flue_runs` with indexed listing by workflow name and status.
Sources: [withastro-flue/packages/runtime/src/runtime/run-store.ts:3-127](), [withastro-flue/packages/runtime/src/sql-run-store.ts:38-66,121-143]()
### Agent submission store and turn journal
Agent durability lives in `AgentSubmissionStore`, a backend-neutral contract covering submission admission, turn journals, stream chunk segments, attempt markers, and lease management.
**Submission states**: `queued` → `running` → `settled`
**Turn journal phases** (checkpoint progression inside one attempt):
```text
before_provider → provider_started → tool_request_recorded → committed
```
Each journal tracks `checkpointLeafId`, optional `toolRequest`, `streamKey`, and commit metadata (`committedLeafId`). Methods like `beginTurnJournal`, `updateTurnJournalPhase`, `commitTurnJournal`, and `replaceTurnJournalAttempt` implement compare-and-set semantics so concurrent coordinators cannot double-commit or steal ownership.
Default durability knobs: `DURABILITY_DEFAULT_MAX_ATTEMPTS = 10`, `DURABILITY_DEFAULT_TIMEOUT_MS = 3_600_000` (1 hour), `LEASE_DURATION_MS = 30_000` (30 seconds).
Sources: [withastro-flue/packages/runtime/src/agent-execution-store.ts:19-26,92-215,256-312]()
### Dispatch queue
`DispatchQueue.enqueue()` admits work durably through the coordinator:
```typescript
// withastro-flue/packages/runtime/src/node/agent-coordinator.ts (createNodeDispatchQueue)
async enqueue(input: DispatchInput): Promise<DispatchReceipt> {
const admission = await coordinator.admitDispatch(input);
// exact replay → original receipt; conflicting replay → throw
// admission persisted in SQL; processing is async via claim loop
}
```
Admission is idempotent keyed by `dispatchId`. The coordinator runs a claim loop with lease heartbeats, reconciles interrupted submissions from a previous process on startup, and enforces **at most one runnable head per session** — later queued work in the same session waits until earlier submissions settle.
Sources: [withastro-flue/packages/runtime/src/runtime/dispatch-queue.ts:3-13](), [withastro-flue/packages/runtime/src/node/agent-coordinator.ts:56-88,369-398](), [withastro-flue/packages/runtime/src/agent-execution-store.ts:228-256]()
### Stream persistence policy
Flue selectively persists streamed events:
- **Buffered** (~3 s flush): `text_delta`, `thinking_start`, `thinking_delta`, `thinking_end` — avoids one storage write per chunk.
- **Excluded entirely**: `turn_request` — would grow storage quadratically and expose full prompts to every stream reader.
Interrupted-stream recovery reads throttled `StreamChunkWriter` segments; `message_end` carries the complete message for history replay.
Sources: [withastro-flue/packages/runtime/src/runtime/run-store.ts:129-165]()
### HTTP surface for workflow runs
The mounted `flue()` sub-app exposes:
| Route | Purpose |
|-------|---------|
| `POST /workflows/:name` | Start a workflow run; default `202` with `streamUrl` and `runId`; `?wait=result` for sync JSON |
| `GET/HEAD /runs/:runId` | Durable Streams protocol read (catch-up, long-poll, SSE) |
| `GET /runs/:runId?meta` | Plain JSON `RunRecord` (status, payload, result, timing) |
| `POST /agents/:name/:id` | Agent prompt admission (`202` + `streamUrl`); not a workflow run |
Run IDs are opaque `run_<ulid>` values. Clients observe live and historical run events at a stable URL without knowing which agent or instance owns the run.
Sources: [withastro-flue/packages/runtime/src/runtime/flue-app.ts:253-296](), [withastro-flue/packages/runtime/test/routing.test.ts:753-794]()
## Side-by-side lifecycle diagrams
### Eve turn lifecycle (workflow-owned)
```mermaid
stateDiagram-v2
[*] --> TurnStarted: user message delivered
TurnStarted --> StepRunning: turnWorkflow → turnStep
StepRunning --> StepCompleted: model + tools finish
StepCompleted --> StepRunning: more tool rounds
StepCompleted --> Parked: HITL / OAuth / subagent wait
Parked --> StepRunning: resumeHook(continuationToken)
StepCompleted --> TurnCompleted: terminal reply
TurnCompleted --> SessionWaiting: conversation mode
TurnCompleted --> SessionCompleted: task mode done
SessionWaiting --> TurnStarted: next message + token
StepRunning --> StepFailed: unrecoverable error
StepFailed --> SessionFailed
```
Sources: [vercel-eve/docs/concepts/sessions-runs-and-streaming.md:37-63](), [vercel-eve/packages/eve/src/execution/turn-workflow.ts:38-56]()
### Flue agent submission lifecycle (store-owned)
```mermaid
stateDiagram-v2
[*] --> Queued: admitDispatch / admitDirect
Queued --> Running: claimSubmission (lease acquired)
Running --> JournalBeforeProvider: beginTurnJournal
JournalBeforeProvider --> ProviderStarted: updateTurnJournalPhase
ProviderStarted --> ToolRecorded: tool_request_recorded
ToolRecorded --> Committed: commitTurnJournal
Committed --> Settled: completeSubmission / failSubmission
Running --> Queued: requeueSubmissionBeforeInputApplied
Running --> Running: replaceTurnJournalAttempt (recovery)
Settled --> [*]
```
Sources: [withastro-flue/packages/runtime/src/agent-execution-store.ts:30-31,94-98,168-215]()
## Portable ideas and integration pitfalls
**What transfers well**
- **Separate resume from inspect handles.** Eve's `continuationToken` vs. `sessionId` and Flue's `dispatchId` vs. `runId` both avoid overloading one identifier for wake-up and observation.
- **Explicit park semantics.** Both frameworks suspend compute during waits rather than polling; clients must understand the parked/waiting signal before sending follow-ups.
- **Idempotent admission.** Flue's `createRun` and `admitDispatch` first-writer-wins patterns mirror Eve's completed-step replay guarantees — safe retries require stable keys.
- **Checkpoint at model boundaries.** Eve steps and Flue turn journal phases both anchor durability where LLM/provider state transitions occur.
**Pitfalls when porting patterns**
| Pitfall | Eve behavior | Flue behavior |
|---------|-------------|---------------|
| Treating `runId` as session ID | Eve uses `sessionId` for both stream and session scope | `runId` is workflow-only; agent work uses instance + session + `dispatchId` |
| Burst message sends | Best-effort fold at workflow boundaries; no durable FIFO | Per-session submission queue with single runnable head |
| Assuming all stream events are persisted | Full NDJSON protocol events are durable in workflow history | `turn_request` excluded; deltas buffered 3 s |
| Custom storage | Not exposed — Workflow SDK owns persistence | `PersistenceAdapter` is the extension point |
| Mid-step side effects | Interrupted steps re-run | Recovery via `replaceTurnJournalAttempt` + attempt markers + lease reconciliation |
## Summary
Eve optimizes for **opinionated, zero-config durability**: every agent session is already a Workflow SDK graph with automatic step checkpoints, parked turns that release compute, and a simple client contract (`continuationToken` + `sessionId`). Authors write tools and state, not workflow code.
Flue optimizes for **composable, store-backed durability**: agent submissions flow through an admitted dispatch queue with turn journals, leases, and startup reconciliation, while workflow runs get first-class `runId` records and HTTP stream endpoints backed by interchangeable databases. The framework makes the split between long-lived agent sessions and finite workflow jobs explicit.
Choose Eve when you want the runtime to own checkpoint boundaries end-to-end. Choose Flue when you need to pick your database, inspect workflow runs over HTTP independently, and reason separately about dispatched agent work versus orchestrated workflow invocations.
---
## 06. Channels, Ingress & Conversation Handles
> Eve channels as authored agent/channels/ routes that own continuationToken and auth policy, versus Flue's first-party channel packages (@flue/slack, etc.), blueprints from flue add, and dispatch-based agent admission over a Hono HTTP surface.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/06-channels-ingress-conversation-handles.md
- Generated: 2026-06-18T19:37:50.972Z
### Source Files
- `vercel-eve:docs/channels/overview.mdx`
- `vercel-eve:docs/channels/custom.mdx`
- `vercel-eve:apps/frameworks/nuxt/agent/channels/eve.ts`
- `withastro-flue:packages/slack/src/index.ts`
- `withastro-flue:blueprints/channel--slack.md`
- `withastro-flue:packages/runtime/src/runtime/dispatch.ts`
- `withastro-flue:.agents/skills/channel-conformance/SKILL.md`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/docs/channels/overview.mdx](vercel-eve/docs/channels/overview.mdx)
- [vercel-eve/docs/channels/custom.mdx](vercel-eve/docs/channels/custom.mdx)
- [vercel-eve/docs/channels/eve.mdx](vercel-eve/docs/channels/eve.mdx)
- [vercel-eve/apps/frameworks/nuxt/agent/channels/eve.ts](vercel-eve/apps/frameworks/nuxt/agent/channels/eve.ts)
- [vercel-eve/packages/eve/src/public/definitions/defineChannel.ts](vercel-eve/packages/eve/src/public/definitions/defineChannel.ts)
- [vercel-eve/packages/eve/src/public/channels/eve.ts](vercel-eve/packages/eve/src/public/channels/eve.ts)
- [withastro-flue/packages/slack/src/index.ts](withastro-flue/packages/slack/src/index.ts)
- [withastro-flue/blueprints/channel--slack.md](withastro-flue/blueprints/channel--slack.md)
- [withastro-flue/packages/runtime/src/runtime/dispatch.ts](withastro-flue/packages/runtime/src/runtime/dispatch.ts)
- [withastro-flue/packages/runtime/src/runtime/flue-app.ts](withastro-flue/packages/runtime/src/runtime/flue-app.ts)
- [withastro-flue/.agents/skills/channel-conformance/SKILL.md](withastro-flue/.agents/skills/channel-conformance/SKILL.md)
</details>
# Channels, Ingress & Conversation Handles
Channels are the edge between an external platform and your agent runtime. Both Eve and Flue solve the same product problem — turn verified inbound traffic into continuing agent work and route replies back — but they split ownership differently. Eve treats channels as **authored agent modules** that call `send()` and own `continuationToken` semantics end to end. Flue treats channels as **first-party ingress packages** mounted on a Hono HTTP surface, with the application admitting work through `dispatch()` and owning outbound behavior.
Understanding where each framework stops (ingress verification vs. session orchestration vs. delivery) is the key to porting patterns between them or choosing an integration style.
## Mental model at a glance
| Concern | Eve (`vercel-eve`) | Flue (`withastro-flue`) |
| --- | --- | --- |
| Channel location | `agent/channels/<name>.ts` (file stem = channel id) | `channels/<name>.ts` (discovered at build time) |
| Channel export | Default export via `defineChannel()` or `eveChannel()` | Named `channel` binding from `createSlackChannel()` etc. |
| Ingress → agent bridge | `send(message, { auth, continuationToken })` inside route handlers | `dispatch(agent, { id, input })` from verified callbacks |
| Conversation handle | `continuationToken` (channel-local raw token, namespaced by file stem) | `id` on dispatch (often `channel.conversationKey(ref)`) |
| Auth policy | Channel route `auth` (e.g. `eveChannel({ auth: [...] })`) | Application-owned; channel verifies provider signatures only |
| Outbound delivery | Channel `events` handlers (e.g. `"message.completed"`) | Application `defineTool()` + provider SDK/Fetch client |
| HTTP mounting | Eve/Nitro routes declared per channel | Hono `flue()` mounts `/channels/:name/:suffix` |
Sources: [vercel-eve/docs/channels/overview.mdx:6-16](), [vercel-eve/docs/channels/custom.mdx:8-12](), [withastro-flue/.agents/skills/channel-conformance/SKILL.md:17-37](), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:293-294]()
## Eve: authored channels that own the session contract
### Where channels live and how they are identified
Eve channels are files under `agent/channels/` on the **root agent only**. The filename stem becomes the channel id: `agent/channels/intake.ts` is addressed as `intake`. Each file default-exports a channel definition; local subagents do not declare channels.
Scaffolding is available through `eve channels add` (interactive or by kind), but channels can also be authored by hand.
Sources: [vercel-eve/docs/channels/overview.mdx:14-27](), [vercel-eve/packages/eve/src/public/definitions/defineChannel.ts:192-196]()
### The channel contract: normalize, resume, deliver
A channel is the edge adapter between a platform and the agent. It:
1. Normalizes platform input into a user message.
2. Owns the `continuationToken` — the resume handle for a conversation on that surface.
3. Decides delivery: how, where, and whether a response goes back.
Built-in platform channels (Slack, Discord, Teams, etc.) and custom channels share this contract. The default Eve HTTP channel (`eveChannel`) is enabled even when `agent/channels/eve.ts` does not exist; that file is typically added only to override auth or hooks.
Sources: [vercel-eve/docs/channels/overview.mdx:6-31]()
### `defineChannel`: routes, `send`, and event-driven delivery
Custom and built-in channels are built with `defineChannel` from `eve/channels`. A minimal custom channel declares HTTP routes and an `events` map:
```ts
// vercel-eve/docs/channels/custom.mdx (excerpt)
export default defineChannel({
routes: [
POST("/message", async (req, { send }) => {
const body = await req.json();
const session = await send(body.message, {
auth: null,
continuationToken: body.token,
});
return Response.json({ sessionId: session.id });
}),
],
events: {
"message.completed"(event, channel, ctx) {
// deliver completed messages back to the surface
},
},
});
```
Route handlers receive helpers including `send`, `getSession`, `receive` (cross-channel hand-off), `params`, `waitUntil`, and `requestIp`. Event handlers receive `(eventData, channel, ctx)` where `channel` carries platform handles plus `continuationToken` and `setContinuationToken`.
Sources: [vercel-eve/docs/channels/custom.mdx:16-56](), [vercel-eve/packages/eve/src/public/definitions/defineChannel.ts:44-86]()
### Continuation tokens: channel-owned, framework-namespaced
Each `send()` call addresses a session by a **channel-local raw token**. The framework prepends the channel name (from the file stem) before handing the token to the runtime — for example, a channel file `stateful.ts` sending with raw token `C1:T1` becomes `stateful:C1:T1` at runtime.
Custom channels define their own token format (Slack uses `channelId:threadTs`, Twilio uses caller/recipient pairs). When identity is not known until later, channels can re-key via `session.setContinuationToken(...)`.
Sources: [vercel-eve/docs/channels/custom.mdx:183-228](), [vercel-eve/packages/eve/src/channel/send.test.ts:167-174]()
### Auth policy on the Eve HTTP channel
The Eve channel exposes canonical session routes under `/eve/v1/session*`. Auth is configured per channel via an ordered `auth` array — the first `AuthFn` returning a `SessionAuthContext` wins; exhaustion rejects with 401.
A production demo might accept anonymous traffic explicitly:
```ts
// vercel-eve/apps/frameworks/nuxt/agent/channels/eve.ts
export default eveChannel({
auth: [localDev(), none()],
});
```
`eveChannel` runs `routeAuth` on every route before dispatching. `onMessage` can further shape session auth and prepend context strings.
Sources: [vercel-eve/docs/channels/eve.mdx:6-16](), [vercel-eve/docs/channels/eve.mdx:48-57](), [vercel-eve/packages/eve/src/public/channels/eve.ts:84-90](), [vercel-eve/packages/eve/src/public/channels/eve.ts:125-133]()
### Cross-channel hand-off
Eve supports pivoting inbound work from one channel to another via `args.receive(targetChannel, { message, target, auth })`. The target channel's `receive` hook owns continuation-token format; the inbound channel does not also start a session on itself.
Sources: [vercel-eve/docs/channels/custom.mdx:99-136]()
## Flue: first-party ingress packages and dispatch-based admission
### Product boundary: what Flue owns vs. the application
Flue's channel conformance skill defines a strict boundary:
**Flue owns:** authenticated verified HTTP ingress; fixed discovered routes beneath `channels/<name>.ts`; provider-native typed payloads; canonical conversation identity where the provider supplies it; predictable Hono-compatible handler results.
**The application owns:** provider SDK/Fetch clients and credentials; `defineTool()` and authorization policy; installation/OAuth/token storage; webhook registration; deduplication and business persistence.
Conversation keys are identifiers, never authorization capabilities.
Sources: [withastro-flue/.agents/skills/channel-conformance/SKILL.md:17-37]()
### First-party channel packages (`@flue/slack`, etc.)
Provider channels ship as npm packages. `@flue/slack` exposes `createSlackChannel()` which:
- Verifies Slack request signatures over exact bytes (5-minute clock skew).
- Handles URL verification internally.
- Registers optional routes: `/events`, `/interactions`, `/commands` (omitted handlers omit routes).
- Provides `conversationKey(ref)` and `parseConversationKey(id)` for canonical thread identity.
```ts
// withastro-flue/packages/slack/src/index.ts (excerpt)
conversationKey(ref) {
return `slack:v1:${encodeURIComponent(ref.teamId)}:...`;
}
```
Provider-native payloads pass through without field renaming or Flue-normalized event models. Filtering bots, subtypes, or event families is application policy.
Sources: [withastro-flue/packages/slack/src/index.ts:27-39](), [withastro-flue/packages/slack/src/index.ts:263-270](), [withastro-flue/packages/slack/src/index.ts:328-331](), [withastro-flue/.agents/skills/channel-conformance/SKILL.md:39-71]()
### Blueprints from `flue add`
Channel blueprints (e.g. `blueprints/channel--slack.md`) scaffold a complete integration slice: install `@flue/slack` and `@slack/web-api`, create `channels/slack.ts`, wire `dispatch()` in the events callback, and bind a reply tool using `channel.parseConversationKey(id)`.
Blueprints teach the intended developer experience — export `channel`, export a project-owned `client`, dispatch verified events to an agent, define narrow outbound tools — without prescribing a model provider.
Sources: [withastro-flue/blueprints/channel--slack.md:9-105](), [withastro-flue/blueprints/channel--slack.md:123-133]()
### Dispatch: agent admission over a continuing session
After ingress verification, the application admits work with `dispatch()`:
```ts
// withastro-flue/blueprints/channel--slack.md (excerpt)
await dispatch(assistant, {
id: channel.conversationKey(thread),
input: {
type: 'slack.app_mention',
eventId: payload.event_id,
text: event.text,
},
});
```
`dispatch()` validates the target agent exists, requires a non-empty `id` (agent instance id), requires JSON-serializable `input`, and enqueues admission. It resolves after queuing — not after model processing — returning a `dispatchId` (not a workflow `runId`).
The agent initializer receives `id` via `AgentCreateContext`, enabling per-thread tool binding:
```ts
export default createAgent(({ id }) => ({
tools: [replyInThread(channel.parseConversationKey(id))],
}));
```
Sources: [withastro-flue/packages/runtime/src/runtime/dispatch.ts:12-47](), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:163-180](), [withastro-flue/packages/runtime/src/types.ts:30-52](), [withastro-flue/packages/runtime/src/types.ts:61-67]()
### Hono HTTP surface and route discovery
At build time, Flue discovers `channels/*.ts` modules and normalizes each channel's `routes` array. At runtime, `flue()` mounts a Hono app with:
- `POST /agents/:name/:id` — direct agent HTTP prompts
- `ALL /channels/:name/:suffix` — channel ingress (e.g. `/channels/slack/events`)
The channel route handler matches `METHOD + path suffix` against the discovered route table. A channel must export a named `channel` binding with at least one route.
Sources: [withastro-flue/packages/cli/src/lib/build.ts:283-316](), [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:59-76](), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:283-294](), [withastro-flue/packages/runtime/src/runtime/flue-app.ts:594-628]()
## Architecture comparison
```mermaid
flowchart TB
subgraph Eve["Eve — agent/channels/<name>.ts"]
PlatformE[External platform]
RouteE[Channel routes GET/POST/WS]
SendE["send(message, { auth, continuationToken })"]
RuntimeE[Eve session runtime]
EventsE["events: message.completed → deliver"]
PlatformE --> RouteE --> SendE --> RuntimeE --> EventsE --> PlatformE
end
subgraph Flue["Flue — channels/<name>.ts + @flue/*"]
PlatformF[External platform]
HonoF["flue() Hono /channels/:name/:suffix"]
VerifyF["@flue/slack verify + forward payload"]
DispatchF["dispatch(agent, { id, input })"]
QueueF[Dispatch queue]
AgentF[createAgent initializer by id]
ToolsF["defineTool() + WebClient"]
PlatformF --> HonoF --> VerifyF --> DispatchF --> QueueF --> AgentF
AgentF --> ToolsF --> PlatformF
end
```
## Conversation handles side by side
| Property | Eve `continuationToken` | Flue dispatch `id` |
| --- | --- | --- |
| Who defines format | Channel author (raw token); framework adds `<channelName>:` prefix | Channel package `conversationKey()` (e.g. `slack:v1:...`) |
| Where it is set | `send({ continuationToken })` or `setContinuationToken()` in events | Passed to `dispatch({ id })`; agent sees it as `context.id` |
| Session coupling | Directly parks/resumes Eve sessions | Selects agent instance within a continuing session |
| Auth coupling | Paired with `auth` on each `send()` | Identifier only; auth is separate application policy |
| Re-keying | `channel.setContinuationToken()` at event boundaries | Application manages id stability via `conversationKey` inputs |
Sources: [vercel-eve/docs/channels/custom.mdx:183-197](), [withastro-flue/packages/slack/src/index.ts:266-269](), [withastro-flue/packages/runtime/src/types.ts:31-33]()
## Ingress verification vs. route auth
Both frameworks verify inbound provider traffic, but auth policy placement differs:
**Eve** folds route auth into the channel definition. `eveChannel({ auth: [...] })` decides who may call `/eve/v1/session*`. Platform channels (Slack signatures, etc.) are implemented inside Eve's built-in channel adapters, and custom channels implement verification in route handlers.
**Flue** keeps verification inside `@flue/*` packages (signature checks, body limits, protocol handshakes) and explicitly leaves workspace allowlists, tenant authorization, and outbound credentials to application code. Short-lived provider capabilities (`trigger_id`, `response_url`) must not be copied into dispatch input or durable session data.
Sources: [vercel-eve/packages/eve/src/public/channels/eve.ts:84-90](), [withastro-flue/.agents/skills/channel-conformance/SKILL.md:55-71](), [withastro-flue/blueprints/channel--slack.md:119-121]()
## Portable patterns
Several ideas transfer cleanly across both models:
1. **Filename-derived channel namespace** — both use the channel file stem as the URL/logical namespace segment (Eve prepends it to tokens; Flue mounts `/channels/<stem>/...`).
2. **Provider-native payloads** — neither normalizes Slack/Discord/etc. into a universal event schema at the channel boundary; application code filters and interprets.
3. **Thread identity as a stable key** — Eve's `slackContinuationToken(channelId, threadTs)` and Flue's `conversationKey({ teamId, channelId, threadTs })` solve the same addressing problem with different string formats.
4. **Separation of ingress and outbound** — Flue makes this explicit (channel verifies in, tools send out). Eve combines them in one module but still separates route ingress from `events`-driven delivery.
What does **not** port directly: Eve's `send()` + `events` delivery loop has no Flue equivalent — Flue requires explicit `dispatch()` admission and application-owned reply tools. Eve's `receive()` cross-channel hand-off is a first-class framework feature; Flue would implement similar logic by dispatching to a different agent `id` or calling another channel callback from application code.
## Summary
Eve channels are **full session adapters** authored in `agent/channels/`: they normalize input, call `send()` with channel-local `continuationToken` values (namespaced by the framework), enforce route `auth`, and deliver responses through lifecycle `events`. Flue channels are **verified ingress libraries** (`@flue/slack`, etc.) wired in `channels/` and mounted on a Hono `/channels/*` surface; they forward provider-native payloads to application callbacks that admit work via `dispatch(agent, { id, input })`, using canonical `conversationKey` strings as agent instance ids. Eve centralizes conversation continuity in the channel module; Flue centralizes it in dispatch admission and agent initialization — with outbound behavior, credentials, and authorization always remaining application-owned.
---
## 07. Sandboxes, Built-in Tools & Skills
> Eve's single per-agent sandbox with shipped harness tools (bash, read_file, load_skill, connection_search) and sandbox proxying, contrasted with Flue's adapter-based sandbox factories (local, Cloudflare, blueprint-backed remote) and explicit tool/skill composition in createAgent.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/07-sandboxes-built-in-tools-skills.md
- Generated: 2026-06-18T19:37:56.494Z
### Source Files
- `vercel-eve:docs/sandbox.mdx`
- `vercel-eve:docs/concepts/default-harness.md`
- `vercel-eve:packages/eve/src/shared/sandbox-backend.ts`
- `withastro-flue:packages/runtime/src/sandbox.ts`
- `withastro-flue:packages/runtime/src/node/local.ts`
- `withastro-flue:packages/runtime/src/cloudflare/cf-sandbox.ts`
- `withastro-flue:blueprints/sandbox--modal.md`
- `withastro-flue:packages/runtime/src/skill-frontmatter.ts`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/docs/sandbox.mdx](vercel-eve/docs/sandbox.mdx)
- [vercel-eve/docs/concepts/default-harness.md](vercel-eve/docs/concepts/default-harness.md)
- [vercel-eve/packages/eve/src/shared/sandbox-backend.ts](vercel-eve/packages/eve/src/shared/sandbox-backend.ts)
- [vercel-eve/packages/eve/src/runtime/framework-tools/bash.ts](vercel-eve/packages/eve/src/runtime/framework-tools/bash.ts)
- [vercel-eve/packages/eve/src/runtime/framework-tools/skill.ts](vercel-eve/packages/eve/src/runtime/framework-tools/skill.ts)
- [withastro-flue/packages/runtime/src/sandbox.ts](withastro-flue/packages/runtime/src/sandbox.ts)
- [withastro-flue/packages/runtime/src/node/local.ts](withastro-flue/packages/runtime/src/node/local.ts)
- [withastro-flue/packages/runtime/src/cloudflare/cf-sandbox.ts](withastro-flue/packages/runtime/src/cloudflare/cf-sandbox.ts)
- [withastro-flue/blueprints/sandbox--modal.md](withastro-flue/blueprints/sandbox--modal.md)
- [withastro-flue/packages/runtime/src/skill-frontmatter.ts](withastro-flue/packages/runtime/src/skill-frontmatter.ts)
- [withastro-flue/packages/runtime/src/agent-definition.ts](withastro-flue/packages/runtime/src/agent-definition.ts)
- [withastro-flue/packages/runtime/src/agent.ts](withastro-flue/packages/runtime/src/agent.ts)
- [withastro-flue/packages/runtime/src/session.ts](withastro-flue/packages/runtime/src/session.ts)
</details>
# Sandboxes, Built-in Tools & Skills
Eve and Flue both give agents an isolated execution environment and a default toolbelt for shell and file work, but they make different architectural bets. Eve ships a **single per-agent sandbox** with a **framework-owned harness**: built-in tools are always present, shell/file tools execute in the app runtime and **proxy** into the sandbox, and skills or connections add opt-in discovery tools. Flue treats the sandbox as a **pluggable adapter** wired explicitly in `createAgent`, normalizes every backend behind a `SessionEnv` interface, and **composes** tools and skills from the agent profile at initialization time.
Understanding this split matters when porting agents between frameworks or designing a provider-neutral harness: Eve optimizes for convention-over-configuration and a unified `/workspace` namespace; Flue optimizes for explicit wiring and backend interchangeability.
## Architectural contrast
```mermaid
flowchart TB
subgraph Eve["Eve (vercel-eve)"]
Harness["Default harness\n(built-in tools)"]
AppRT["App runtime\n(tool executors)"]
SandboxBE["SandboxBackend\n(vercel / docker / microsandbox / just-bash)"]
WS["/workspace\n(single sandbox per agent)"]
Harness --> AppRT
AppRT -->|"proxy: bash, read_file, …"| SandboxBE
SandboxBE --> WS
end
subgraph Flue["Flue (withastro-flue)"]
CA["createAgent(() => ({ sandbox, tools, skills }))"]
SF["SandboxFactory\n(local / bash / cloudflare / blueprint)"]
SE["SessionEnv\n(exec, readFile, writeFile, …)"]
CT["createTools(env)\nor adapter.tools"]
CA --> SF
SF --> SE
SE --> CT
end
```
Sources: [vercel-eve/docs/concepts/default-harness.md:25-40](), [vercel-eve/packages/eve/src/runtime/framework-tools/bash.ts:50-52](), [withastro-flue/packages/runtime/src/types.ts:835-850](), [withastro-flue/packages/runtime/src/agent.ts:39-49]()
| Dimension | Eve | Flue |
|-----------|-----|------|
| Sandbox count | Exactly one per agent | One `SessionEnv` per initialized harness; task sessions can scope `cwd` |
| Wiring | Automatic; override via `agent/sandbox/sandbox.ts` | Explicit: `createAgent(() => ({ sandbox: local() }))` |
| Backend model | `SandboxBackend` with `prewarm` + `create` lifecycle | `SandboxFactory` → `SessionEnv`; remote providers implement `SandboxApi` |
| Built-in tools | Shipped with harness; override/disable by filename | `createTools(env)` unless adapter supplies `tools` factory |
| Skills tool | `load_skill` (reads from sandbox) | `activate_skill` (lazy instruction load) |
| External integrations | `connection_search` when connections declared | MCP tools merged via `tools` array; no built-in connection search |
## Eve: one sandbox, proxied harness tools
### Single sandbox per agent
Every Eve agent has exactly one sandbox rooted at `/workspace`. The built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` tools target it automatically, and authored runtime code reaches the same environment through `ctx.getSandbox()`. The path namespace is stable across backends so `/workspace/foo` resolves consistently whether the backend is local Docker or hosted Vercel Sandbox.
Sources: [vercel-eve/docs/sandbox.mdx:6-21](), [vercel-eve/docs/sandbox.mdx:39-41]()
### Sandbox backends and lifecycle
Eve selects where the sandbox runs through a pluggable `SandboxBackend` interface. Built-in factories include `vercel()`, `docker()`, `microsandbox()`, `justbash()`, and availability-aware `defaultBackend()`. The backend implements a two-phase lifecycle: `prewarm` captures reusable template state at build time (bootstrap hooks, seed files), and `create` opens or reattaches a durable session at runtime.
Authors customize via `defineSandbox` with optional `bootstrap` (template-scoped) and `onSession` (per-session) hooks, seed files under `agent/sandbox/workspace/`, and network policy on the backend factory or in `onSession`.
Sources: [vercel-eve/packages/eve/src/shared/sandbox-backend.ts:121-164](), [vercel-eve/docs/sandbox.mdx:111-131](), [vercel-eve/docs/sandbox.mdx:160-182]()
### Built-in tools and sandbox proxying
The default harness registers a fixed tool set. Shell and filesystem tools are notable because their **executors live in the app runtime** but their **effects land in the sandbox**:
| Tool | Effect location | Notes |
|------|-----------------|-------|
| `bash`, `read_file`, `write_file`, `glob`, `grep` | Sandbox | Proxied via `requireSandboxSession()` / `ctx.getSandbox()` |
| `web_fetch`, `todo`, `ask_question`, `agent` | App runtime | No sandbox involvement |
| `web_search` | Provider | No local executor |
| `load_skill` | App runtime executor; reads sandbox FS | Present when agent declares skills |
| `connection_search` | App runtime | Present when agent declares connections |
The `bash` tool illustrates the proxy pattern: the framework executor calls `executeBashOnSandbox(await requireSandboxSession(), input)` rather than running shell commands in the host process.
Sources: [vercel-eve/docs/concepts/default-harness.md:25-46](), [vercel-eve/packages/eve/src/runtime/framework-tools/bash.ts:50-56]()
Authors can override a built-in by authoring `agent/tools/<slug>.ts` with the same slug, spread defaults from `eve/tools/defaults`, or disable one with `disableTool()`. A typo in the disable filename fails at build time.
Sources: [vercel-eve/docs/concepts/default-harness.md:49-78]()
### Skills: `load_skill`
Skills live under `agent/skills/`. Eve advertises each skill's description in the prompt and registers `load_skill` when skills are declared. Loading pulls the skill's markdown from the sandbox filesystem (skills are seeded under `/workspace/skills/`) and appends instructions to the active turn. Loading adds **instructions only** — no new execution surface.
Sources: [vercel-eve/docs/skills.mdx:8-18](), [vercel-eve/packages/eve/src/runtime/framework-tools/skill.ts:14-32](), [vercel-eve/docs/concepts/default-harness.md:44-45]()
### Connections: `connection_search`
When an agent declares connections, Eve registers a dynamic `connection__search` tool (model-facing name `connection_search`). Search surfaces connection tools by qualified name (e.g. `connection__linear__list_issues`), which the model can then call directly. This is separate from the static framework tool list.
Sources: [vercel-eve/docs/concepts/default-harness.md:39-46](), [vercel-eve/packages/eve/src/runtime/framework-tools/index.ts:33-38]()
## Flue: adapter factories and explicit composition
### `SandboxFactory` and `SessionEnv`
Flue does not assume a sandbox exists. The agent initializer returned from `createAgent` must supply a `sandbox` field — a `SandboxFactory` whose `createSessionEnv()` produces a `SessionEnv` with `exec`, filesystem operations, `cwd`, and `resolvePath`. All adapters — local host, just-bash, Cloudflare Sandbox, or blueprint-installed remote SDKs — converge on this interface.
```ts
// Local Node target
createAgent(() => ({ sandbox: local(), model: 'anthropic/claude-sonnet-4-6' }));
// Cloudflare Durable Object stub
createAgent(({ id, env }) => ({
sandbox: cloudflareSandbox(getSandbox(env.Sandbox, id)),
}));
// just-bash in-process
createAgent(() => ({ sandbox: bash(() => new Bash({ fs })) }));
```
Sources: [withastro-flue/packages/runtime/src/node/local.ts:16-21](), [withastro-flue/packages/runtime/src/cloudflare/cf-sandbox.ts:37-56](), [withastro-flue/packages/runtime/src/sandbox.ts:116-124](), [withastro-flue/packages/runtime/src/types.ts:835-850]()
### Adapter layering
Flue's `sandbox.ts` module implements the adapter stack:
1. **`bash(factory)`** — wraps a just-bash `BashFactory` into `SessionEnv`
2. **`createSandboxSessionEnv(api, cwd)`** — wraps any `SandboxApi` (remote provider) with path resolution, parent-dir creation on write, and centralized abort checks
3. **`local()`** — thin factory over host `child_process` + filesystem (Node target)
4. **`cloudflareSandbox(stub)`** — maps Cloudflare Sandbox DO methods onto `SandboxApi`
Remote adapters implement `SandboxApi.exec` with `timeoutMs` as the primary cancellation contract; `AbortSignal` is optional and honored when the provider SDK supports it.
Sources: [withastro-flue/packages/runtime/src/sandbox.ts:198-312](), [withastro-flue/packages/runtime/src/cloudflare/cf-sandbox.ts:59-181]()
### Blueprint-backed remote sandboxes
For providers without a first-party Flue export, blueprints document how to write an adapter. The Modal blueprint, for example, wraps a user-owned Modal Sandbox into `SandboxFactory` by implementing `SandboxApi` — shelling out for `stat`, `readdir`, `mkdir`, and `rm` where the SDK only exposes `exec` and `open`. The user owns sandbox lifecycle (create, configure, tear down); Flue only adapts.
Sources: [withastro-flue/blueprints/sandbox--modal.md:15-33](), [withastro-flue/blueprints/sandbox--modal.md:96-279]()
### Built-in tools: composed from `SessionEnv`
Unlike Eve's always-on harness, Flue builds the default tool list in `createTools(env)` at session initialization:
| Tool | Backing |
|------|---------|
| `read` | `env.stat` / `env.readFile` (+ packaged skill paths) |
| `write` | `env.writeFile` |
| `edit` | read + write on `env` |
| `bash` | `env.exec` |
| `grep` | `env.exec` (probes for `rg`, falls back to `grep`) |
| `glob` | `env.exec` |
| `task` | subagent delegation (appended when subagents exist) |
A `SandboxFactory` may replace the entire default list via an optional `tools` factory. The session layer always appends `task` and, when skills exist, `activate_skill`. Adapter-supplied tools must not collide with reserved names.
Sources: [withastro-flue/packages/runtime/src/agent.ts:39-49](), [withastro-flue/packages/runtime/src/session.ts:1220-1264](), [withastro-flue/packages/runtime/src/types.ts:829-833]()
### Explicit tool and skill composition in `createAgent`
`createAgent` merges capabilities from an optional `profile` and per-init options:
```ts
createAgent(() => ({
sandbox: local(),
model: 'anthropic/claude-sonnet-4-6',
instructions: '…',
skills: [mySkill],
tools: [myCustomTool, ...mcpTools],
subagents: [researchProfile],
}));
```
`resolveAgentProfile` concatenates `skills`, `tools`, and `subagents` arrays from profile and runtime config. Nothing is implicit — if you omit `tools`, you get sandbox defaults plus framework additions (`task`, `activate_skill`), not a hidden web-search or connection layer.
Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:72-104]()
### Skills: `activate_skill` and frontmatter validation
Flue discovers skills from `.agents/skills/<name>/SKILL.md` at session `cwd` and merges them with explicitly packaged skills. The system prompt instructs the model to call `activate_skill` before work that matches a skill description. `parseSkillMarkdown` enforces Agent Skills frontmatter rules: required `name` and `description`, name-directory matching, `allowed-tools` parsing, and tolerant handling of unknown metadata fields.
Sources: [withastro-flue/packages/runtime/src/context.ts:48-105](), [withastro-flue/packages/runtime/src/skill-frontmatter.ts:18-72](), [withastro-flue/packages/runtime/src/agent.ts:333-370]()
## Portable patterns and migration notes
**Unified workspace root.** Eve standardizes on `/workspace` across all backends. Flue adapters pick their own default `cwd` (`/workspace` for Cloudflare, configurable for Modal, host path for `local()`). When porting, align `cwd` and path resolution semantics.
**Proxy vs direct execution.** Eve's separation keeps tool policy (read-before-write, compaction resets) in the app runtime while isolating command effects. Flue's tools call `SessionEnv` directly — simpler stack, but the adapter must implement the full filesystem/exec contract.
**Skill activation.** Both frameworks use lazy skill loading and Agent Skills-compatible `SKILL.md` layout, but Eve reads skills from the sandbox (`load_skill` + `/workspace/skills/`) while Flue activates from discovered catalog paths (`activate_skill`). Skill *instructions* never add tools in either framework; behavior still comes from the agent's existing tool set.
**Backend extensibility.** Eve's `SandboxBackend` owns build-time prewarm and runtime reconnect metadata. Flue's `SandboxApi` + blueprint pattern pushes provider lifecycle to the user and focuses the adapter on conforming `SessionEnv`. A Flue Modal adapter and an Eve `defineSandbox({ backend: vercel() })` solve the same isolation problem with inverted ownership boundaries.
**Provider neutrality.** Both designs stay BYOC/BYOK friendly: Eve's `defaultBackend()` chain and custom `SandboxBackend` implementations avoid locking to one host; Flue's `SandboxFactory` slot accepts any adapter that satisfies `SessionEnv`, with skills sourced from local directories or packaged catalogs rather than a proprietary connector.
## Summary
Eve treats the sandbox as a first-class framework resource — one per agent, seeded, lifecycle-managed, and reached through proxied harness tools (`bash`, `read_file`, …) plus opt-in `load_skill` and `connection_search`. Flue treats the sandbox as an explicit `createAgent` dependency — adapter-selected, `SessionEnv`-normalized, with tools and skills composed from profile arrays and optional adapter overrides. Eve favors batteries-included defaults; Flue favors interchangeable backends and visible composition. Both keep skills as lazy instruction loads separate from the execution surface, making skill packs portable across file, repository, or catalog sources without tying them to a specific model provider.
---
## 08. Comparison Verdict & Portable Ideas
> Closing synthesis: where Eve wins (Vercel-native durability, filesystem discoverability, opinionated harness defaults) versus where Flue wins (multi-target deploy, explicit workflow surface, channel package ecosystem); which patterns—skills, sandbox isolation, session streaming—transfer across either stack.
- Page Markdown: https://grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/pages/08-comparison-verdict-portable-ideas.md
- Generated: 2026-06-18T19:37:36.759Z
### Source Files
- `vercel-eve:README.md`
- `withastro-flue:README.md`
- `vercel-eve:docs/concepts/security-model.md`
- `vercel-eve:packages/eve/package.json`
- `withastro-flue:packages/runtime/package.json`
- `withastro-flue:packages/postgres/package.json`
- `withastro-flue:packages/opentelemetry/package.json`
- `vercel-eve:Dockerfile`
<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [vercel-eve/README.md](vercel-eve/README.md)
- [vercel-eve/Dockerfile](vercel-eve/Dockerfile)
- [vercel-eve/docs/concepts/security-model.md](vercel-eve/docs/concepts/security-model.md)
- [vercel-eve/docs/concepts/execution-model-and-durability.md](vercel-eve/docs/concepts/execution-model-and-durability.md)
- [vercel-eve/docs/concepts/default-harness.md](vercel-eve/docs/concepts/default-harness.md)
- [vercel-eve/docs/concepts/sessions-runs-and-streaming.md](vercel-eve/docs/concepts/sessions-runs-and-streaming.md)
- [vercel-eve/docs/reference/project-layout.md](vercel-eve/docs/reference/project-layout.md)
- [vercel-eve/packages/eve/package.json](vercel-eve/packages/eve/package.json)
- [withastro-flue/README.md](withastro-flue/README.md)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/packages/runtime/package.json](withastro-flue/packages/runtime/package.json)
- [withastro-flue/packages/postgres/package.json](withastro-flue/packages/postgres/package.json)
- [withastro-flue/packages/opentelemetry/package.json](withastro-flue/packages/opentelemetry/package.json)
- [withastro-flue/packages/sdk/src/public/stream.ts](withastro-flue/packages/sdk/src/public/stream.ts)
- [withastro-flue/packages/runtime/src/skill-frontmatter.ts](withastro-flue/packages/runtime/src/skill-frontmatter.ts)
- [withastro-flue/apps/docs/src/content/docs/api/routing-api.md](withastro-flue/apps/docs/src/content/docs/api/routing-api.md)
</details>
# Comparison Verdict & Portable Ideas
Eve and Flue both ship a TypeScript agent harness with skills, sandboxes, durable sessions, and channel ingress — but they optimize for different deployment and authoring models. Eve is a **filesystem-first** framework where the `agent/` directory is the contract and durability is on by default through the Workflow SDK. Flue is a **programmable harness** where agents and workflows are distinct TypeScript modules, compiled for multiple runtimes, with a broad channel and persistence package ecosystem.
This page closes the multi-repo comparison: where each framework wins, where they converge, and which patterns transfer regardless of which stack you choose.
## Verdict at a Glance
| Dimension | Eve (`vercel/eve`) | Flue (`withastro/flue`) |
| --- | --- | --- |
| **Authoring model** | Directory contract: `instructions.md`, `skills/`, `tools/`, path-derived names | TypeScript modules: `agents/<name>.ts`, `workflows/<name>.ts`, `createAgent()` factories |
| **Durability** | Sessions durable by default; Workflow SDK checkpoints every step | Durable sessions + explicit workflow runs with persisted `runId` |
| **Deploy targets** | Vercel-native (Nitro host, Vercel Sandbox, Vercel Workflow) | Node, Cloudflare Workers, GitHub Actions, GitLab CI, Render, Daytona |
| **HTTP surface** | Stable `/eve/v1/session` API with `continuationToken` + `sessionId` | Mountable `flue()` Hono app: `/agents/:name/:id`, `/workflows/:name`, `/channels/:name/*` |
| **Channels** | Built into `eve` package exports (`./channels/slack`, etc.) | 20+ standalone channel packages (`@flue/slack`, `@flue/stripe`, …) |
| **Persistence** | Framework-owned workflow state | Pluggable adapters (`@flue/postgres`, `@flue/mysql`, `@flue/redis`, …) |
| **Streaming** | NDJSON event stream per session | Durable Streams protocol with offset-based replay |
| **Harness defaults** | Rich built-in tool set, compaction, `ask_question`, subagent delegation | Harness composed in `createAgent(() => ({ ... }))`; sandbox via `local()` or adapters |
Sources: [vercel-eve/README.md:19-57](), [withastro-flue/README.md:58-76](), [withastro-flue/AGENTS.md:7-20]()
## Where Eve Wins
### Vercel-native durability without configuration
Eve treats every turn as a durable workflow. Sessions survive crashes, timeouts, and redeploys with no author configuration — the runtime owns `start()`, `resumeHook()`, and checkpoint boundaries. Parked work (approvals, OAuth, subagents) suspends compute until input arrives.
```text
User message
│
▼
Channel (auth, continuationToken)
│
▼
Harness (one AI unit of work → { session, next })
│
▼
Runtime (persist state, stream events, workflow primitives)
│
▼
Workflow SDK checkpoints at each step boundary
```
Sources: [vercel-eve/docs/concepts/execution-model-and-durability.md:16-28](), [vercel-eve/README.md:48-57]()
### Filesystem discoverability
The agent directory **is** the API. Path determines identity — no `name` field on `define*` calls. `eve info` and `eve build` emit inspectable artifacts under `.eve/` (`agent-discovery-manifest.json`, `diagnostics.json`, `compiled-agent-manifest.json`), making it straightforward to debug what the runtime will load before booting a server.
Sources: [vercel-eve/docs/reference/project-layout.md:6-17](), [vercel-eve/README.md:41-42](), [vercel-eve/README.md:134-136]()
### Opinionated harness defaults
Every Eve agent ships with a framework-owned loop and a full built-in tool table: `bash`, `read_file`, `write_file`, `glob`, `grep`, `web_fetch`, `todo`, `ask_question`, `agent` (subagent), `load_skill`, and conditional `connection_search`. Compaction at 90% context threshold is automatic. Authors extend or override; they do not assemble the loop from scratch.
Sources: [vercel-eve/docs/concepts/default-harness.md:6-40]()
### Security model tuned for production agents
Eve draws a hard trust boundary between **app runtime** (secrets, Node.js, unrestricted network) and **sandbox** (isolated `/workspace`, no `process.env`). Built-in file/shell tools proxy from runtime into sandbox; custom tools execute in runtime. Credential brokering, connection token caching, constant-time channel signature verification, and fail-closed auth are documented as first-class concepts.
Sources: [vercel-eve/docs/concepts/security-model.md:8-53](), [vercel-eve/Dockerfile:8-76]()
## Where Flue Wins
### Multi-target deploy and runtime adapters
Flue compiles projects into deployable server artifacts for Node **and** Cloudflare Workers (with `./cloudflare` and `./node` entrypoints), plus documented targets for GitHub Actions, GitLab CI, Render, and Daytona sandboxes. The runtime package itself exposes adapter-specific subpaths rather than assuming one host.
Sources: [withastro-flue/README.md:58-65](), [withastro-flue/packages/runtime/package.json:12-48]()
### Explicit agents vs. workflows surface
Flue separates **persistent agent instances** (sessions across direct prompts and dispatched inputs) from **finite workflow runs** (`workflows/<name>.ts` exports `run(...)` with unique `ctx.id === runId`). Runs are workflow-only; agent prompts operate within sessions. This split makes background automations and interactive agents first-class, addressable concepts rather than optional harness features.
```text
agents/<name>.ts workflows/<name>.ts
createAgent(...) export run(...)
│ │
▼ ▼
Harness → Session Workflow run (runId)
(persistent) (finite, persisted)
│ │
▼ ▼
POST /agents/:name/:id POST /workflows/:name
GET /agents/:name/:id GET /runs/:runId
```
Sources: [withastro-flue/AGENTS.md:7-20](), [withastro-flue/apps/docs/src/content/docs/api/routing-api.md:44-56]()
### Channel package ecosystem
Flue distributes channels as standalone packages — Slack, Discord, Teams, GitHub, Stripe, Shopify, Twilio, Zendesk, and more — each with its own build, tests, and Cloudflare worker conformance. This modular layout lets teams add or upgrade one ingress provider without pulling the entire runtime. Eve consolidates channel exports inside the monolithic `eve` package.
Sources: [withastro-flue/README.md:67-76](), [withastro-flue/packages/runtime/package.json:1-49]()
### Pluggable persistence and observability
Persistence and telemetry are opt-in adapter packages: `@flue/postgres`, `@flue/mysql`, `@flue/redis`, `@flue/mongodb`, `@flue/libsql` for storage, and `@flue/opentelemetry` as a peer-dependent tracing adapter. Operators choose backing stores and exporters; the core runtime stays host-agnostic.
Sources: [withastro-flue/packages/postgres/package.json:1-39](), [withastro-flue/packages/opentelemetry/package.json:1-42]()
### Typed client SDK and Durable Streams
`@flue/sdk` wraps Durable Streams with offset-based replay, live tailing, and automatic reconnection. Agent prompts return `202 { streamUrl, offset }`; workflow runs return `202 { runId, streamUrl, offset }`. The SDK exposes `FlueEventStream` as an `AsyncIterable` with checkpoint semantics — a different protocol than Eve's NDJSON-per-line stream, but equally explicit about resume coordinates.
Sources: [withastro-flue/packages/sdk/src/public/stream.ts:1-47](), [withastro-flue/apps/docs/src/content/docs/api/routing-api.md:58-62]()
## Portable Patterns
These ideas transfer across either stack without lock-in to a single vendor or host.
### Skills as markdown expertise packs
Both frameworks treat skills as **markdown with YAML frontmatter** aligned to the Agent Skills ecosystem. Eve discovers `agent/skills/<name>.md` (or packaged `skills/<name>/SKILL.md`) and loads them on demand via `load_skill`. Flue imports `SKILL.md` with a `with { type: 'skill' }` attribute and validates frontmatter through `parseSkillMarkdown`, mirroring `skills-ref` name and description rules.
| Concern | Eve | Flue |
| --- | --- | --- |
| Discovery | Filesystem walk under `agent/skills/` | Import assertion or workspace discovery |
| Load trigger | Model calls `load_skill` | `session.skill(name)` or harness `skills` array |
| Frontmatter safety | YAML-only; `---js` fences disabled | FAILSAFE_SCHEMA YAML; unknown fields ignored |
Sources: [vercel-eve/docs/concepts/security-model.md:47-49](), [vercel-eve/docs/reference/project-layout.md:16](), [withastro-flue/packages/runtime/src/skill-frontmatter.ts:18-51](), [withastro-flue/README.md:8-10]()
Skill packs remain **provider-neutral**: they are plain files in a repository or catalog, not tied to a model API. A Grok-Wiki or similar knowledge surface can index the same `SKILL.md` trees regardless of which harness loads them.
### Sandbox isolation with shared tooling DNA
Both frameworks give agents an isolated workspace for shell and file operations. Eve proxies `bash`/`read_file`/`write_file` from app runtime into a per-agent sandbox (Vercel Sandbox microVM on Vercel, Docker/`just-bash`/`microsandbox` locally). Flue's `local()` factory binds directly to the host on Node and shares `just-bash` as a dependency. Eve's `Dockerfile` seeds a `vercel-sandbox` user with `/workspace` — evidence of opinionated sandbox image defaults.
Sources: [vercel-eve/docs/concepts/security-model.md:10-19](), [vercel-eve/docs/concepts/default-harness.md:25-33](), [vercel-eve/Dockerfile:61-76](), [withastro-flue/packages/runtime/package.json:78](), [withastro-flue/packages/runtime/src/node/index.ts:1-7]()
The portable lesson: **keep secrets and privileged API calls in the trusted runtime; let the model manipulate files only inside an isolated workspace.**
### Session streaming as a first-class contract
Both frameworks separate **submission/resume handles** from **observation handles** and expose event streams for UIs and automations.
| Handle | Eve | Flue |
| --- | --- | --- |
| Resume conversation | `continuationToken` (channel-owned) | Session persists across `POST /agents/:name/:id` |
| Stream/inspect | `sessionId` → `GET .../stream` (NDJSON) | `streamUrl` + `offset` (Durable Streams) |
| Workflow runs | Optional experimental `Workflow` tool | `POST /workflows/:name` → `GET /runs/:runId` |
Sources: [vercel-eve/docs/concepts/sessions-runs-and-streaming.md:8-33](), [withastro-flue/apps/docs/src/content/docs/api/routing-api.md:46-56]()
Client integrations should treat stream coordinates as opaque, support reconnection from the last acknowledged offset or event index, and never conflate "send next message" with "attach to event stream."
### Subagents and delegation
Eve's built-in `agent` tool delegates to a copy sharing the parent sandbox. Declared subagents in `subagents/` get separate sandboxes, skills, and durable sessions. Flue's harness supports task agents and subagent profiles through the same `createAgent` composition model. The portable pattern: **scope context and filesystem per delegation boundary; persist child work as its own session or run.**
Sources: [vercel-eve/docs/concepts/default-harness.md:38-44](), [vercel-eve/docs/concepts/execution-model-and-durability.md:38-40](), [withastro-flue/README.md:51]()
## Choosing Between Them
```mermaid
flowchart TD
subgraph question [Start here]
Q1{Vercel-primary deploy?}
Q2{Need Cloudflare Workers or CI-native runs?}
Q3{Prefer directory authoring or TS module composition?}
end
Q1 -->|Yes| EveFit[Eve: durable-by-default, Vercel Sandbox, eve info discovery]
Q1 -->|No| Q2
Q2 -->|Yes| FlueFit[Flue: multi-target adapters, workflow runs, channel packages]
Q2 -->|No| Q3
Q3 -->|Directory / markdown-first| EveFit
Q3 -->|Explicit agents + workflows in code| FlueFit
```
**Choose Eve** when you want markdown-first authoring, zero-config durable sessions on Vercel Workflow, inspectable `.eve/` discovery artifacts, and a production security model with Vercel Sandbox credential brokering baked in.
**Choose Flue** when you need deploy portability (especially Cloudflare Workers), a clean split between long-lived agents and finite workflow runs, a wide channel adapter catalog, and bring-your-own persistence/telemetry.
**Borrow from both** when designing your own harness: filesystem-discoverable skills, strict sandbox/runtime trust boundaries, separated resume vs. stream handles, and durable checkpointing at tool boundaries are proven patterns in both codebases.
## Summary
Eve optimizes for **opinionated defaults on a Vercel-shaped runtime** — the agent folder is the source of truth, durability is automatic, and the harness ships ready to work. Flue optimizes for **explicit composition and deploy flexibility** — agents and workflows are distinct HTTP surfaces, channels and stores are modular packages, and the SDK speaks Durable Streams. Skills (Agent Skills markdown), sandbox isolation, and session/event streaming are the highest-value portable ideas: they are file- and protocol-level patterns that survive a change of host, model provider, or harness implementation.
---