# Subagents and schedules

> Local subagents under agent/subagents/, remote agents with defineRemoteAgent, cron schedules (defineSchedule .ts and .md), dev schedule dispatch route, and nested delegation boundaries.

- Repository: vercel/eve
- GitHub: https://github.com/vercel/eve
- Human docs: https://grok-wiki.com/public/docs/vercel-eve-759e1d74a10f
- Complete Markdown: https://grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/llms-full.txt

## Source Files

- `docs/subagents.mdx`
- `docs/schedules.mdx`
- `packages/eve/src/compiler/normalize-subagent.ts`
- `packages/eve/src/compiler/normalize-schedule.ts`
- `packages/eve/src/execution/subagent-adapter.ts`
- `packages/eve/src/internal/nitro/routes/dev-schedule-dispatch.ts`

---

---
title: "Subagents and schedules"
description: "Local subagents under agent/subagents/, remote agents with defineRemoteAgent, cron schedules (defineSchedule .ts and .md), dev schedule dispatch route, and nested delegation boundaries."
---

Eve exposes delegation and time-based execution as filesystem slots: local specialists under `agent/subagents/`, remote deployments via `defineRemoteAgent`, and cron schedules under `agent/schedules/`. The compiler lowers each subagent to a model-visible tool with a shared `{ message, outputSchema? }` input shape; schedules compile to Nitro task handlers that start durable sessions on their cron cadence (or on demand through the dev-only dispatch route).

## Delegation surfaces

| Surface | Location | Runtime behavior |
| ------- | -------- | ---------------- |
| Built-in `agent` tool | Shipped with every agent (override at `agent/tools/agent.ts`) | Copies the current agent; shares parent sandbox and tools |
| Local subagent | `agent/subagents/<id>/agent.ts` with `defineAgent` | Isolated child agent; own instructions, tools, skills, sandbox |
| Remote subagent | `agent/subagents/<id>/agent.ts` with `defineRemoteAgent` | Async `POST` to another Eve deployment; parent parks until callback |
| Schedule | `agent/schedules/<name>.{ts,md}` | Cron-fired session; markdown (task mode) or handler (`run`) |

All subagent kinds lower to the same tool input schema:

```ts
{
  message: string;       // all context the child needs; no parent history
  outputSchema?: object; // when set, child runs in task mode
}
```

Set `outputSchema` (on the definition or per call) to require structured output as the tool result.

## Built-in `agent` tool

Every agent receives an `agent` tool by default. The model calls it to delegate a subtask to a copy of the current agent:

- Shares the parent's sandbox, tools, connections, and instructions.
- Starts with fresh conversation history and fresh `defineState` data.
- File writes from parallel `agent` calls are immediately visible to the parent — useful for fan-out across files.
- If a declared subagent calls `agent`, the child is a copy of *that* subagent, not the root.

An authored tool at `agent/tools/agent.ts` takes priority over the built-in.

## Local subagents

A declared subagent lives under `agent/subagents/<id>/` and exports `defineAgent` from `agent.ts`. Its directory under `subagents/` is the only marker that makes it a subagent; the tool name is the path-derived `<id>` with no prefix (unlike connection tools).

<ParamField body="description" type="string" required>
Model-visible delegation hint on `defineAgent`. The compiler rejects subagents that omit it.
</ParamField>

:::files
agent/subagents/researcher/
├── agent.ts            # required (must export description)
├── instructions.md     # or instructions.ts, optional
├── tools/              # optional
├── skills/             # optional
├── connections/        # optional
├── hooks/              # optional
├── sandbox/            # optional
└── subagents/          # optional, nested subagents
:::

The compiler recurses depth-first through nested `subagents/` directories (`compileSubagentGraph`), producing a flat node list and parent→child edges. Each nested child is compiled as its own agent root with the same isolation rules.

<Warning>
Subagent names share the runtime tool namespace with authored tools. A subagent named `researcher` collides with `agent/tools/researcher.ts` — Eve rejects the build rather than picking a winner.
</Warning>

### Isolation boundary

A declared subagent inherits nothing from the root's authored slots. Discovery treats `agent/subagents/<id>/` as its own agent root; absent slots fall back to framework defaults, not the parent's versions.

| Slot | Built-in `agent` tool | Declared subagent |
| ---- | ----------------------- | ----------------- |
| Instructions | Inherited (copy) | Own `instructions.{md,ts}`, optional |
| Tools | Inherited | Own `tools/` |
| Connections | Inherited | Own `connections/` |
| Skills | Inherited | Own `skills/` |
| Sandbox | Shared with parent | Own `sandbox/`, else framework default |
| Hooks | Inherited | Own `hooks/` |
| State | Fresh | Fresh |
| Channels | Root-only | Root-only |
| Schedules | Root-only | Root-only |

`channels/` and `schedules/` are not supported inside local subagents. Duplicate shared procedures by copying skill markdown into each child's `skills/`, or share typed helpers via `lib/`.

## Remote subagents

Use `defineRemoteAgent` when the specialist is a separately deployed Eve agent, not a directory in your repo. The file still lives under `agent/subagents/<id>/`, but only `agent.ts` is allowed — the compiler rejects local package entries (`tools/`, `skills/`, `connections/`, etc.) on remote definitions.

```ts title="agent/subagents/weather.ts"
import { defineRemoteAgent } from "eve";
import { vercelOidc } from "eve/agents/auth";

export default defineRemoteAgent({
  url: "https://weather-agent.example.com",
  description: "Answers weather, temperature, forecast, wind, rain, and snow questions.",
  auth: vercelOidc(),
});
```

<ParamField body="url" type="string" required>
Base URL of the remote Eve deployment.
</ParamField>

<ParamField body="description" type="string" required>
Model-visible delegation description (same role as local subagents).
</ParamField>

<ParamField body="auth" type="OutboundAuthFn">
Outbound auth hook from `eve/agents/auth` (`vercelOidc`, `bearer`, `basic`).
</ParamField>

<ParamField body="path" type="string">
Route appended to `url` for the create-session request. Defaults to `/eve/v1/session`.
</ParamField>

<ParamField body="outputSchema" type="StandardSchema | JSON Schema">
Structured return type enforced by the remote in task mode.
</ParamField>

Remote dispatch is asynchronous:

1. Parent starts a task-mode session on the remote's `POST /eve/v1/session`, passing a framework callback URL.
2. Parent turn parks until the remote posts a terminal callback.
3. Parent resumes and surfaces the result as a tool outcome.

Failed starts return inline errors. Remotes that start then fail post a terminal failure callback. The parent stream carries `subagent.called`, `action.result`, and `subagent.completed` — for remote calls, `subagent.called.data.remote.url` records the target.

## Nested delegation boundaries

Each delegated subagent (local or remote) starts its own child session in **task mode** via `buildSubagentRunInput`. Key boundaries:

**Parent stream vs child stream.** The parent stream emits only control-plane events `subagent.called` and `subagent.completed`. Read `subagent.called.data.childSessionId` and subscribe at `GET /eve/v1/session/:childSessionId/stream` for full child progress.

**Lineage metadata.** `RunInput.parent` carries `callId`, `sessionId`, `turn`, and `rootSessionId`. Nested chains propagate `rootSessionId` from the top user-facing session so every descendant attributes to the same root in one hop.

**HITL proxying.** Child `input.requested` events route through `SUBAGENT_ADAPTER`, which forwards them to the parent via durable `resumeHook`. The parent channel renders HITL prompts; responses route back to the matching child by `childContinuationToken`. Concurrent nested descendants each own separate proxy entries on the parent.

**Sandbox sharing.** Only the built-in `agent` tool (self-delegation) forwards `parentSandboxState` and `sandboxSessionId` on the adapter state. Declared subagents get their own sandbox unless they author `sandbox/`.

**Capabilities.** Parent `SessionCapabilities` forward verbatim through the subagent chain so HITL readiness flows transparently.

```mermaid
sequenceDiagram
  participant Parent as Parent session
  participant Adapter as SUBAGENT_ADAPTER
  participant Child as Child session
  participant Hook as resumeHook

  Parent->>Child: dispatchRuntimeActionsStep (task mode)
  Parent-->>Parent: subagent.called (childSessionId)
  Child->>Adapter: input.requested (HITL)
  Adapter->>Hook: forward to parentContinuationToken
  Hook->>Parent: resume parked turn
  Parent-->>Parent: render HITL, collect response
  Parent->>Child: route response by childContinuationToken
  Child-->>Parent: subagent.completed
```

## Schedules

Schedules start the root agent on a cron cadence instead of waiting for inbound messages. Each schedule is a single file under `agent/schedules/`; the name is path-derived (`agent/schedules/billing/sweep.ts` → `"billing/sweep"`). Nested directories are supported. Schedules are **root-only** — declared subagents cannot author `schedules/`.

### `defineSchedule`

Import from `eve/schedules`. Provide `cron` and exactly one of `markdown` or `run`:

```ts
interface ScheduleDefinition {
  cron: string;
  markdown?: string; // fire-and-forget prompt (task mode)
  run?: (args: ScheduleHandlerArgs) => Promise<void> | void;
}

interface ScheduleHandlerArgs {
  receive: CrossChannelReceiveFn;
  waitUntil: (task: Promise<unknown>) => void;
  appAuth: SessionAuthContext;
}
```

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

`defineSchedule` is a type-level pass-through; the compiler enforces the one-of rule.

### Markdown form (fire-and-forget)

Minimal schedule: Eve runs the agent on the prompt and discards the output. The session uses `SCHEDULE_ADAPTER` in **task mode** — it runs to completion or fails, and cannot park for HITL or OAuth.

<Tabs>
  <Tab title="TypeScript">
```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.",
});
```
  </Tab>
  <Tab title="Markdown">
```md title="agent/schedules/cleanup.md"
---
cron: "0 0 * * 0"
---

Sweep stale workflow state.
```
  </Tab>
</Tabs>

Markdown files take `cron` in frontmatter; the body is the prompt.

### Handler form (`run`)

Use a handler when the schedule must deliver to a channel, branch on conditions, or compute arguments at fire time. Handler sessions use the same durable runtime as any other session and **can park** (for example, waiting for a Slack reply after `receive`).

```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,
      }),
    );
  },
});
```

| Handler arg | Purpose |
| ----------- | ------- |
| `receive(channel, { message, target, auth })` | Start a session on another channel (same contract as route handlers) |
| `waitUntil(promise)` | Extend the cron task lifetime past handler return so parked sessions settle |
| `appAuth` | Pre-built app principal (`{ authenticator: "app", principalId: "eve:app", principalType: "runtime" }`) |

Wrap `receive` calls in `waitUntil` so Nitro awaits background work before the task ends.

### Dev schedule dispatch

`eve dev` never fires schedules on their cron cadence. Trigger one manually through the dev-only dispatch route, which runs the same path as production (`dispatchScheduleTask` → `ScheduleDispatcher.trigger`):

:::endpoint POST /eve/v1/dev/schedules/:scheduleId
Dev-only one-shot schedule dispatch. Re-resolves schedules from disk on every call so authored-source watcher edits apply without restart. No auth (local dev server only). Production builds never mount this route.
:::

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

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

`:scheduleId` is the path-derived schedule name. URL-encode `/` in nested names (`billing%2Fsweep`). Unknown ids return `404` with `availableScheduleIds`. Subscribe to each returned session at `GET /eve/v1/session/:sessionId/stream`.

### Production (Vercel)

Hosted builds register each compiled schedule as a Nitro scheduled task. The `cron` expression is written to `.vercel/output/config.json` as a Vercel Cron Job entry. Confirm discovery under **Settings → Cron Jobs** and execution under **Observability → Cron Jobs**.

<Note>
`eve dev` does not run cron. Use the dev dispatch route to exercise schedules locally.
</Note>

## When to use what

| Need | Use |
| ---- | --- |
| Same agent, parallel file work | Built-in `agent` tool |
| Different prompt, tools, or sandbox | Local subagent under `subagents/<id>/` |
| Specialist owned by another deployment | `defineRemoteAgent` |
| Optional procedure, same identity | Skill (`load_skill`) — lighter than a subagent |
| Periodic agent work | Schedule under `agent/schedules/` |
| Schedule that hands off to Slack/Discord/etc. | Schedule handler with `receive` + `waitUntil` |

## Related pages

<CardGroup>
  <Card title="Execution model" href="/execution-model">
    Session/turn/step nesting, durable checkpoints, and parked work (HITL, OAuth, subagents).
  </Card>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    continuationToken contracts, NDJSON events, and child-session stream attachment.
  </Card>
  <Card title="Project layout" href="/project-layout">
    Authored slots, path-derived naming, and subagent inheritance rules.
  </Card>
  <Card title="Context control" href="/context-control">
    Subagent context isolation and on-demand skills.
  </Card>
  <Card title="Channels" href="/channels">
    Deliver schedule output to users via `receive`.
  </Card>
  <Card title="HTTP API" href="/http-api">
    Stable session routes and dev-only schedule-dispatch endpoint.
  </Card>
</CardGroup>
