# Eve authoring surfaces

> Configure `agent.ts`, write instructions, tools, skills, connections, sandbox overrides, subagents, and schedules from the filesystem contract.

- Repository: withastro/flue-with-vercel-eve
- GitHub: https://github.com/withastro/flue
- Human docs: https://grok-wiki.com/public/docs/withastro-flue-with-vercel-eve-f4b79875fff6
- Complete Markdown: https://grok-wiki.com/public/docs/withastro-flue-with-vercel-eve-f4b79875fff6/llms-full.txt

## Source Files

- `vercel-eve:docs/agent-config.md`
- `vercel-eve:docs/tools.mdx`
- `vercel-eve:docs/skills.mdx`
- `vercel-eve:docs/connections.mdx`
- `vercel-eve:docs/sandbox.mdx`
- `vercel-eve:docs/subagents.mdx`
- `vercel-eve:docs/reference/typescript-api.md`

---

---
title: "Eve authoring surfaces"
description: "Configure `agent.ts`, write instructions, tools, skills, connections, sandbox overrides, subagents, and schedules from the filesystem contract."
---

Eve compiles an agent by walking the `agent/` directory: each subdirectory is an authored slot, and the file path—not a `name` or `id` field—determines runtime identity. Discovery runs at build time, lowers every slot into a compiled manifest under `.eve/`, and exposes the result through `eve info` and the HTTP session API. Model routing stays provider-neutral: pass a gateway model id string or any AI SDK `LanguageModel` instance in `agent.ts`.

## Filesystem contract

Identity is path-derived. A tool at `agent/tools/get_weather.ts` registers as `get_weather`; a connection at `agent/connections/linear.ts` registers as `linear`; a subagent at `agent/subagents/researcher/agent.ts` registers as `researcher`. The root agent name comes from `package.json` `name`, falling back to the app-root directory name.

:::files
my-agent/
├── package.json
├── agent/
│   ├── agent.ts              # runtime config (optional on root)
│   ├── instructions.md       # required on root agent
│   ├── tools/
│   ├── skills/
│   ├── connections/
│   ├── sandbox/              # or sandbox.ts shorthand
│   ├── schedules/            # root-only
│   ├── subagents/
│   ├── channels/             # root-only
│   ├── hooks/
│   ├── lib/                  # import-only helpers
│   └── instrumentation.ts    # root-only
└── evals/
:::

| Slot | Authored at | Subagents | Notes |
| --- | --- | --- | --- |
| Runtime config | `agent.ts` | Yes | `model` required when present; `description` required for subagents |
| Instructions | `instructions.md`, `instructions.ts`, or `instructions/` | Optional | Required on root; always-on system prompt |
| Tools | `tools/<name>.ts` | Yes | Run in app runtime; filename is the model-facing name |
| Skills | `skills/<name>.md` or packaged directory | Yes | Loaded on demand via `load_skill` |
| Connections | `connections/<name>.ts` | Yes | MCP or OpenAPI; credentials never reach the model |
| Sandbox | `sandbox.ts` or `sandbox/sandbox.ts` + `workspace/` | Yes | Isolated `/workspace`; framework default when omitted |
| Schedules | `schedules/<name>.ts` or `.md` | No | Root-only; cron-driven task or handler |
| Subagents | `subagents/<id>/` | Yes | Own slots; inherits nothing from root |

<Note>
Compared to Flue's code-first `createAgent` modules under `.flue/` or `src/`, Eve keeps capabilities in filesystem slots that discovery compiles into a manifest. Both frameworks separate always-on prompts from on-demand procedures, but Eve enforces the split through `instructions/` vs `skills/` directories.
</Note>

## `agent.ts` — runtime config

`agent/agent.ts` calls `defineAgent` from `eve`. Omit the file entirely and Eve defaults to `anthropic/claude-sonnet-4.6`. When present, `model` is required.

<CodeGroup>
```ts title="Gateway model id"
import { defineAgent } from "eve";

export default defineAgent({
  model: "anthropic/claude-opus-4.8",
});
```

```ts title="Direct provider instance"
import { anthropic } from "@ai-sdk/anthropic";
import { defineAgent } from "eve";

export default defineAgent({
  model: anthropic("claude-opus-4.8"),
});
```
</CodeGroup>

<ParamField body="model" type="LanguageModel | string" required>
Primary model for agent turns. Accepts an AI Gateway model id or any AI SDK-compatible `LanguageModel`.
</ParamField>

<ParamField body="modelOptions" type="AgentModelOptionsDefinition">
Provider option overrides forwarded to the model call (for example `providerOptions`).
</ParamField>

<ParamField body="compaction" type="AgentCompactionDefinition">
Conversation compaction config. Defaults to enabled at `thresholdPercent: 0.9`. Lower the threshold to compact sooner.
</ParamField>

<ParamField body="outputSchema" type="StandardJSONSchemaV1 | JsonObject">
Structured return type for task-mode runs (subagent, schedule, remote job). Interactive turns ignore it unless the client supplies a per-message schema.
</ParamField>

<ParamField body="experimental" type="{ codeMode?: boolean }">
Unstable opt-in flags. `codeMode` routes executable tools through a sandboxed code-execution wrapper.
</ParamField>

<ParamField body="build" type="{ externalDependencies?: string[] }">
Hosted-build packaging. Listed packages stay external while Eve compiles authored modules.
</ParamField>

<ParamField body="description" type="string">
Required for subagents. Surfaced to the parent as the lowered subagent tool's description.
</ParamField>

## Instructions

Instructions are the always-on system prompt, prepended to every model call. The root agent requires `agent/instructions.md` (or `instructions.ts` / `instructions/`). Subagents treat instructions as optional.

```md title="agent/instructions.md"
You are a concise assistant. Use tools when they are available.
```

For build-time composition, switch to TypeScript:

```ts title="agent/instructions.ts"
import { defineInstructions } from "eve/instructions";

export default defineInstructions({
  markdown: "You are a concise assistant. Use tools when they are available.",
});
```

| Surface | Loaded | Use for |
| --- | --- | --- |
| `instructions.md` / `.ts` | Every turn | Identity, tone, standing rules |
| `agent/skills/*` | On demand via `load_skill` | Situational procedures |

Split large prompts across `agent/instructions/` (non-recursive). Root file content comes first, then directory entries sorted alphabetically. You cannot author both `instructions.md` and `instructions.ts` at the root—that pairing is a build error.

## Tools

Tools are typed actions the agent calls. Filename under `agent/tools/` is the model-facing name. Tools execute in the **app runtime** with full `process.env` access—not inside the sandbox.

```ts title="agent/tools/get_weather.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
  description: "Get the current weather for a city.",
  inputSchema: z.object({ city: z.string().min(1) }),
  async execute({ city }, ctx) {
    return { city, condition: "Sunny", temperatureF: 72 };
  },
});
```

<ParamField body="description" type="string" required>
What the tool does, written for the model.
</ParamField>

<ParamField body="inputSchema" type="Zod schema | Standard Schema | JsonObject" required>
Input shape. Use `z.object({})` for no input.
</ParamField>

<ParamField body="execute" type="(input, ctx) => T | Promise<T>" required>
Implementation. Receives runtime context while authored code is running.
</ParamField>

<ParamField body="needsApproval" type="ApprovalPredicate">
Human-in-the-loop gate. Helpers from `eve/tools/approval`: `never()`, `once()`, `always()`, or a custom predicate.
</ParamField>

<ParamField body="toModelOutput" type="(output) => ModelOutput">
Project rich return data down to what the model sees. Channels still receive the full output.
</ParamField>

### Runtime context in tools

| Member | Use |
| --- | --- |
| `ctx.session` | Session metadata, turn, auth, parent lineage |
| `ctx.getSandbox()` | Live sandbox handle for `/workspace` |
| `ctx.getSkill(id)` | Read packaged skill metadata and files |
| `ctx.getToken()` | Resolve bearer token for tool `auth` |
| `ctx.requireAuth()` | Force authorization flow (for example after a `401`) |

Eve never runs tools during discovery. Completed steps replay recorded results; interrupted steps re-run—make non-idempotent side effects idempotent or gate them with approval.

## Skills

Skills are on-demand procedures the model loads with the framework-owned `load_skill` tool. Eve advertises each skill's description; the full body enters context only when the model calls `load_skill`.

```md title="agent/skills/forecast.md"
Use the weather tool before answering forecast or temperature questions.
```

Packaged skills use a directory with `SKILL.md` and sibling files:

```md title="agent/skills/research/SKILL.md"
---
description: Research unfamiliar topics before answering with confidence.
---

When the task is novel or ambiguous, gather evidence first.
```

For typed or generated content, use `defineSkill` from `eve/skills`:

```ts title="agent/skills/research.ts"
import { defineSkill } from "eve/skills";

export default defineSkill({
  description: "Research unfamiliar topics before answering with confidence.",
  markdown: "When the task is novel, gather evidence first.",
  files: {
    "references/checklist.md": "# Checklist\n\n- Find primary sources.\n",
  },
});
```

<Warning>
Skills are scoped per agent. A subagent's `skills/` are invisible to the root agent. Share executable helpers through `lib/`, not a shared-skill mechanism.
</Warning>

Skills add instructions only—never a new execution surface. For typed runtime behavior, author a tool instead.

## Connections

Connections wire external MCP or OpenAPI servers into the agent. Files live under `agent/connections/`; the filename is the connection name. The model never sees URLs or credentials—it discovers tools through `connection__search` and calls them as `connection__<connection>__<tool>`.

```ts title="agent/connections/linear.ts"
import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace: issues, projects, cycles, and comments.",
  auth: {
    getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
  },
});
```

| Connection type | Helper | Import |
| --- | --- | --- |
| MCP (Streamable HTTP or SSE) | `defineMcpClientConnection` | `eve/connections` |
| OpenAPI 3.x | `defineOpenAPIConnection` | `eve/connections` |

Connection-level options mirror tools: `auth.getToken`, `headers`, `tools.allow` / `tools.block` (MCP), `operations.allow` / `operations.block` (OpenAPI), and `approval` from `eve/tools/approval`. Tokens resolve per step and never land in conversation history.

For per-user OAuth, use `connect()` from `@vercel/connect/eve` or `defineInteractiveAuthorization` for self-hosted flows. Throw `ConnectionAuthorizationRequiredError` from `getToken` to start consent; map mid-flight `401` responses to `ctx.requireAuth()` so cached tokens are evicted.

## Sandbox overrides

Every agent has exactly one sandbox—an isolated bash environment rooted at `/workspace`. Built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` already target it. Override only to add setup, seed files, pick a backend, or lock down network.

Seed files by placing them under `agent/sandbox/workspace/`:

```text
agent/sandbox/
  sandbox.ts
  workspace/
    schema.sql          → /workspace/schema.sql
    scripts/run.sh      → /workspace/scripts/run.sh
```

Author `defineSandbox` from `eve/sandbox`:

```ts title="agent/sandbox/sandbox.ts"
import { defineSandbox } from "eve/sandbox";
import { vercel } from "eve/sandbox/vercel";

export default defineSandbox({
  backend: vercel({ runtime: "node24", resources: { vcpus: 2 } }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use();
    await sandbox.run({ command: "apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use({ networkPolicy: "deny-all" });
  },
});
```

Two layouts exist: `agent/sandbox.ts` (definition only) and `agent/sandbox/sandbox.ts` (definition + `workspace/` seeding). When both exist, the folder layout wins.

| Backend | Import | Runs |
| --- | --- | --- |
| `vercel()` | `eve/sandbox/vercel` | Vercel Sandbox |
| `docker()` | `eve/sandbox/docker` | Local Docker container |
| `microsandbox()` | `eve/sandbox/microsandbox` | Local lightweight VM |
| `justbash()` | `eve/sandbox/justbash` | Pure-JS simulated bash |
| `defaultBackend()` | `eve/sandbox` | Best available: Vercel → Docker → microsandbox → just-bash |

Lifecycle hooks: `bootstrap({ use })` runs once per template (reusable setup); `onSession({ use, ctx })` runs once per durable session (network policy, per-user credentials). `/workspace` persists across turns for the same session.

## Subagents

Subagents delegate focused subtasks. Two kinds exist:

| Kind | Mechanism | Sandbox | Tools/skills |
| --- | --- | --- | --- |
| Built-in `agent` tool | Copy of the calling agent | Shared with parent | Inherited |
| Declared subagent | `agent/subagents/<id>/` | Own sandbox (or default) | Own slots only |

Declared subagents require `description` in `agent.ts`:

```ts title="agent/subagents/researcher/agent.ts"
import { defineAgent } from "eve";

export default defineAgent({
  description: "Investigate ambiguous questions before the parent agent responds.",
  model: "anthropic/claude-opus-4.8",
});
```

A declared subagent inherits **nothing** from the root. Discovery treats `subagents/<id>/` as its own agent root with optional `instructions`, `tools/`, `connections/`, `skills/`, `sandbox/`, `hooks/`, and nested `subagents/`. `channels/` and `schedules/` are root-only.

Eve lowers every subagent into a model-visible tool with input `{ message: string; outputSchema?: object }`. A declared subagent's tool name is the bare path-derived id (`researcher`). Subagent names collide with tool names at build time—Eve rejects the build.

<Info>
Use a [skill](/eve-authoring-surfaces) when the agent keeps its identity and needs only an optional procedure. Split a subagent when the task needs a different prompt, narrower tool surface, or isolated runtime context.
</Info>

## Schedules

Schedules start the agent on a cron cadence. Each is a single file under `agent/schedules/` (root-only). Nested directories are supported; the name is path-derived (`agent/schedules/billing/sweep.ts` → `billing/sweep`).

Every schedule provides `cron` and exactly one of `markdown` or `run`:

<ParamField body="cron" type="string" required>
Standard 5-field cron (`minute hour day-of-month month day-of-week`). Vercel evaluates in UTC.
</ParamField>

<ParamField body="markdown" type="string">
Fire-and-forget prompt. Runs in task mode—cannot park for HITL or OAuth.
</ParamField>

<ParamField body="run" type="(args) => void | Promise<void>">
Handler with `receive`, `waitUntil`, and `appAuth` for channel handoffs.
</ParamField>

```ts title="agent/schedules/heartbeat.ts"
import { defineSchedule } from "eve/schedules";

export default defineSchedule({
  cron: "*/5 * * * *",
  markdown: "Pull open Linear issues and POST a summary to the metrics endpoint.",
});
```

Markdown schedules can also be plain `.md` files with frontmatter:

```md title="agent/schedules/cleanup.md"
---
cron: "0 0 * * 0"
---

Sweep stale workflow state.
```

Handler form delivers to a channel:

```ts title="agent/schedules/daily-digest.ts"
import { defineSchedule } from "eve/schedules";
import slack from "../channels/slack.js";

export default defineSchedule({
  cron: "0 9 * * 1-5",
  async run({ receive, waitUntil, appAuth }) {
    waitUntil(
      receive(slack, {
        message: "Summarize yesterday's activity and post the digest.",
        target: { channelId: "C0123ABC" },
        auth: appAuth,
      }),
    );
  },
});
```

`eve dev` never fires schedules on their cron cadence. Trigger one while iterating:

<RequestExample>
```sh
curl -X POST http://localhost:3000/eve/v1/dev/schedules/heartbeat
```
</RequestExample>

<ResponseExample>
```json
{ "scheduleId": "heartbeat", "sessionIds": ["..."] }
```
</ResponseExample>

## Author a complete agent

<Steps>
<Step title="Scaffold the layout">
Create `agent/` with `instructions.md` and optionally `agent.ts`. Run `eve init` if starting from scratch.
</Step>

<Step title="Add capabilities">
Place tools in `agent/tools/`, skills in `agent/skills/`, and external integrations in `agent/connections/`. Seed sandbox files under `agent/sandbox/workspace/` when the agent needs starter files.
</Step>

<Step title="Configure runtime">
Set `model` and optional `compaction`, `modelOptions`, or `outputSchema` in `agent.ts`. Add `defineSandbox` only when the default sandbox is insufficient.
</Step>

<Step title="Add delegation and automation">
Declare specialists under `agent/subagents/<id>/` with required `description`. Add cron jobs under `agent/schedules/` for recurring work.
</Step>

<Step title="Verify discovery">
Run `eve info` (or `eve info --json`) to list discovered tools, skills, subagents, schedules, routes, and diagnostics. Inspect compiled artifacts under `.eve/` when discovery reports errors.
</Step>
</Steps>

## `define*` helper map

| Helper | Import from | Authored at |
| --- | --- | --- |
| `defineAgent` | `eve` | `agent/agent.ts` |
| `defineTool` | `eve/tools` | `agent/tools/<name>.ts` |
| `defineSkill` | `eve/skills` | `agent/skills/<name>.ts` |
| `defineInstructions` | `eve/instructions` | `agent/instructions.ts` |
| `defineMcpClientConnection`, `defineOpenAPIConnection` | `eve/connections` | `agent/connections/<name>.ts` |
| `defineSandbox` | `eve/sandbox` | `agent/sandbox.ts` or `agent/sandbox/sandbox.ts` |
| `defineSchedule` | `eve/schedules` | `agent/schedules/<name>.ts` |
| `defineDynamic` | `eve/tools`, `eve/skills`, `eve/instructions` | Per-session resolution |

## Common discovery failures

| Symptom | Likely cause |
| --- | --- |
| Tool not listed in `eve info` | File outside `agent/tools/` or missing default export |
| Subagent build error | Missing `description` in `subagents/<id>/agent.ts` |
| Name collision | Subagent directory name matches an existing tool name |
| `instructions` build error | Both `instructions.md` and `instructions.ts` at root |
| Schedule not found | File in subagent directory (`schedules/` is root-only) |
| Sandbox seed rejected | `agent/sandbox/workspace/skills/` conflicts with `agent/skills/` |

## Related pages

<CardGroup>
<Card title="Eve project layout" href="/eve-project-layout">
Authored slots under `agent/`, path-derived naming, workspace seeding rules, and subagent package boundaries.
</Card>
<Card title="Eve quickstart" href="/eve-quickstart">
Run `eve init`, start `eve dev`, inspect discovery with `eve info`, and send the first session message.
</Card>
<Card title="Eve sessions and streaming" href="/eve-sessions-streaming">
Start sessions, stream NDJSON events, send follow-ups with `continuationToken`, and handle HITL and subagent events.
</Card>
<Card title="Eve weather fixture" href="/eve-weather-fixture">
Walk through the `weather-agent` fixture: instructions, tool schema, skill procedure, and local dev smoke paths.
</Card>
<Card title="Build Flue agents" href="/build-flue-agents">
Flue's code-first alternative: `createAgent`, `defineAgentProfile`, tools, skills, and sandboxes in TypeScript modules.
</Card>
<Card title="Eve CLI reference" href="/eve-cli-reference">
Commands and flags for `eve init`, `info`, `build`, `dev`, and schedule dispatch during local iteration.
</Card>
</CardGroup>
