An agent file has 4 top-level blocks: `ellipsis`, optional `trigger`, optional `input`, and `session`. Set `ellipsis.kind` to `agent`. Unknown fields fail validation. See [Agents](/docs/agents) for how files deploy and run.

## Scheduled test checks

```yaml title=".ellipsis/agents/nightly-tests.yaml"
ellipsis:
  kind: agent
  name: nightly-tests
  description: Check the API test suite every night

trigger:
  type: cron
  schedule: '0 2 * * *'

session:
  claude_code:
    prompt: |
      Run the tests in api-repo. Report failures with the test
      name and error. Do not modify code.
    model: claude-opus-5-5
  environment:
    repositories:
      - name: api-repo
  permissions:
    github:
      permissions: read_only
  budget:
    session: 3
    day: 10
    week: 50
```

Every night at 02:00 UTC, a session runs the tests and reports failures without changing code.

## React to a pull request

```yaml title=".ellipsis/agents/check-api-changes.yaml"
ellipsis:
  kind: agent
  name: check-api-changes

trigger:
  type: react
  pull_request:
    on: [pushed]
    repositories: [api-repo]
    paths: ['src/routes/**']
    draft: false

session:
  claude_code:
    model: claude-opus-5-5
    prompt: |
      Check the changed API routes for missing test coverage.
      Add focused regression tests where needed, run them,
      and report the results.
  environment:
    repositories:
      - name: api-repo
  budget:
    session: 5
```

Each push to a non-draft pull request in `api-repo` that changes `src/routes/` starts a session. The trigger decides which events match; the environment decides which repositories are checked out.

## Typed input and output

```yaml title=".ellipsis/agents/classify-change.yaml"
ellipsis:
  kind: agent
  name: classify-change

input:
  json_schema:
    type: object
    properties:
      description:
        type: string
    required: [description]
    additionalProperties: false
  message: 'Classify this change: {{description}}'

session:
  claude_code:
    prompt: |
      Classify the requested change as bugfix, feature, or maintenance.
      Explain the classification in one sentence.
    model: claude-opus-5-5
  output:
    json_schema:
      type: object
      properties:
        category:
          type: string
          enum: [bugfix, feature, maintenance]
        reason:
          type: string
      required: [category, reason]
      additionalProperties: false
  budget:
    session: 1
```

Start it with `{"input":{"description":"Reject expired reset tokens"}}` instead of a prompt. Read the validated result from `GET /v1/sessions/{session_id}/output`.

Structured output works with the models that list it in `GET /v1/account/models`.

An agent with a trigger can declare `input` too. The trigger fires on its event as before; `json_schema` applies when you start the agent from the dashboard, CLI, or API. One `message` template serves both, so every `{{field}}` in it must be a field of the event and of the schema: declare the event's field names in the schema. The template then reads the event when the trigger fires and the input when you start it. Omit `message` to keep each door's default: the event's details on a trigger, your input as JSON on a start.

```yaml title=".ellipsis/agents/issue-triage.yaml"
ellipsis:
  kind: agent
  name: issue-triage

trigger:
  type: react
  linear_issue:
    on: [opened]

input:
  json_schema:
    type: object
    properties:
      issue:
        type: object
        properties:
          identifier:
            type: string
        required: [identifier]
    required: [issue]
    additionalProperties: false
  message: 'Triage {{issue.identifier}}.'

session:
  claude_code:
    prompt: |
      Reproduce the issue, label it, and comment with a proposed fix.
    model: claude-opus-5-5
```

When a Linear issue opens, the message renders from the event: `Triage API-214.`. Starting the agent with `{"input":{"issue":{"identifier":"API-214"}}}` renders the same message from your input, so a nightly job can run it on an issue you choose.

## Add a Codex skill

A skill is a directory with a `SKILL.md` file that gives Codex a reusable procedure and its supporting files:

```yaml fragment
session:
  codex:
    prompt: Use the release-checks skill to review the release.
    model: gpt-6-sol
  skills:
    - path: skills/release-checks
      repository:
        name: api-repo
```

`SKILL.md` needs YAML frontmatter with a `description`. Ellipsis reads the skill from the session's checkout of that repository, or from its default branch when the repository isn't in the session. A missing or invalid skill fails the session.

A session can have up to 10 skills. Each skill can have up to 50 files, 64 KiB per file, and 512 KiB in total.

## Budgets

```yaml fragment
session:
  claude_code:
    model: claude-opus-5-5
  budget:
    session: 5
    day: 20
    week: 100
    month: 300
```

Values are US dollars. `session` caps each session. `day`, `week`, and `month` cap this agent's total spend over the last 1, 7, and 28 days. Handlers take the same fields. Account limits also apply; see [Budgets](/docs/budgets).

## Fields

<span id="identity-and-triggers" />
<span id="session-settings" />

| Field | Type | Meaning |
| --- | --- | --- |
| `ellipsis` | `object`, required | Identifies the file. |
| `ellipsis.kind` | `string`, required | `agent`. One of `agent`. |
| `ellipsis.name` | `string` | Agent name, unique within the account. |
| `ellipsis.description` | `string` | Description shown in the dashboard. |
| `ellipsis.enabled` | `boolean` | Whether the agent is active. Default `true`. |
| `ellipsis.metadata` | `object` | `labels` and `annotations` for your own use. |
| `ellipsis.metadata.labels` | `list of string` | Short tags. |
| `ellipsis.metadata.annotations` | `map of string` | Free text values by key. |
| `trigger` | `object` | One cron or react trigger; omit to start only on demand. See [Triggers](/docs/agents/triggers). |
| `trigger.type` | `string` | `cron` runs on a schedule; `react` runs on matching events. One of `cron`, `react`. |
| `trigger.enabled` | `boolean` | `false` pauses the trigger and keeps the agent startable; see [Pause a trigger](/docs/agents/triggers#pause-a-trigger). Default `true`. |
| `trigger.schedule` | `string` | A 5 field cron expression in UTC; see [Schedules](/docs/agents/triggers#schedules). `cron` only. |
| `trigger.pull_request` | `object` | Pull request events to match; see [Pull requests](/docs/agents/triggers#pull-requests). |
| `trigger.pull_request.on` | `list of string`, required | Which events fire. One of `opened` (Opened, reopened, or marked ready for review), `pushed` (New commits, including when the pull request opens), `merged` (Merged), `closed` (Closed without merging), `review_submitted` (A review is submitted), `commented` (A comment is added). |
| `trigger.pull_request.repositories` | `list of string or object` | Repository names, or an object with `include` and `exclude` lists. Default every installed repository. |
| `trigger.pull_request.repositories.include` | `list of string` | Repository names to match. Default every repository of the installation, including ones added later. |
| `trigger.pull_request.repositories.exclude` | `list of string` | Repository names to skip, applied after `include`. |
| `trigger.pull_request.base` | `list of string` | Base branches to match. Exact names, prefixes ending in `*`, and `default`. |
| `trigger.pull_request.head` | `list of string` | Head branches to match. Exact names, prefixes ending in `*`, and `default`. |
| `trigger.pull_request.draft` | `boolean` | `true` matches only draft pull requests, `false` only ready ones. Default both. |
| `trigger.pull_request.labels` | `list of string` | Labels match when any listed label is present. |
| `trigger.pull_request.paths` | `list of string` | Globs of changed files to match. `!` patterns aren't allowed. Default every change. |
| `trigger.pull_request.for` | `object` | Which authors count. |
| `trigger.pull_request.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.pull_request.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.pull_request.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.pull_request.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.pull_request.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.pull_request.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.pull_request.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.push` | `object` | Branch pushes to match; see [Branch pushes](/docs/agents/triggers#branch-pushes). Push triggers have no `on` field. |
| `trigger.push.repositories` | `list of string or object` | Repository names, or an object with `include` and `exclude` lists. Default every installed repository. |
| `trigger.push.repositories.include` | `list of string` | Repository names to match. Default every repository of the installation, including ones added later. |
| `trigger.push.repositories.exclude` | `list of string` | Repository names to skip, applied after `include`. |
| `trigger.push.branch` | `list of string` | Branches to match. Exact names, prefixes ending in `*`, and `default`. |
| `trigger.push.paths` | `list of string` | Globs of changed files to match. `!` patterns aren't allowed. Default every change. |
| `trigger.push.for` | `object` | Which authors count. |
| `trigger.push.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.push.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.push.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.push.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.push.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.push.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.push.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.issue` | `object` | GitHub issue events to match; see [Issues](/docs/agents/triggers#issues). |
| `trigger.issue.on` | `list of string`, required | Which events fire. One of `opened` (Opened), `closed` (Closed), `commented` (A comment is added). |
| `trigger.issue.repositories` | `list of string or object` | Repository names, or an object with `include` and `exclude` lists. Default every installed repository. |
| `trigger.issue.repositories.include` | `list of string` | Repository names to match. Default every repository of the installation, including ones added later. |
| `trigger.issue.repositories.exclude` | `list of string` | Repository names to skip, applied after `include`. |
| `trigger.issue.labels` | `list of string` | Labels match when any listed label is present. |
| `trigger.issue.for` | `object` | Which authors count. |
| `trigger.issue.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.issue.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.issue.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.issue.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.issue.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.issue.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.issue.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.check_run` | `object` | CI checks to match; see [CI checks](/docs/agents/triggers#ci-checks). Needs the Checks permission on the GitHub App installation. |
| `trigger.check_run.on` | `list of string` | Which outcomes fire. Default `[failed]`. One of `failed` (The check concluded `failure` or `timed_out`). |
| `trigger.check_run.repositories` | `list of string or object` | Repository names, or an object with `include` and `exclude` lists. Default every installed repository. |
| `trigger.check_run.repositories.include` | `list of string` | Repository names to match. Default every repository of the installation, including ones added later. |
| `trigger.check_run.repositories.exclude` | `list of string` | Repository names to skip, applied after `include`. |
| `trigger.check_run.names` | `list of string` | Check names to match. Exact names and prefixes ending in `*`. Default every check. |
| `trigger.check_run.branch` | `list of string` | Branches the check ran on. Exact names, prefixes ending in `*`, and `default`. |
| `trigger.check_run.for` | `object` | Which authors count. |
| `trigger.check_run.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.check_run.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.check_run.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.check_run.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.check_run.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.check_run.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.check_run.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.release` | `object` | Releases to match; see [Releases](/docs/agents/triggers#releases). |
| `trigger.release.on` | `list of string` | Which events fire. Default `[published]`. One of `published` (A release or prerelease is published, including a draft published later). |
| `trigger.release.repositories` | `list of string or object` | Repository names, or an object with `include` and `exclude` lists. Default every installed repository. |
| `trigger.release.repositories.include` | `list of string` | Repository names to match. Default every repository of the installation, including ones added later. |
| `trigger.release.repositories.exclude` | `list of string` | Repository names to skip, applied after `include`. |
| `trigger.release.tag` | `list of string` | Tags to match. Exact names and prefixes ending in `*`. Default every tag. |
| `trigger.release.prerelease` | `boolean` | `true` matches only prereleases, `false` only full releases. Default both. |
| `trigger.release.for` | `object` | Which authors count. |
| `trigger.release.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.release.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.release.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.release.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.release.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.release.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.release.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.linear_issue` | `object` | Linear issue events to match; see [Linear issues](/docs/agents/triggers#linear-issues). Issues created by bots don't count. |
| `trigger.linear_issue.on` | `list of string` | Which events fire. Default `[opened]`. One of `opened` (Created). |
| `trigger.linear_issue.for` | `object` | Which authors count. |
| `trigger.linear_issue.for.users` | `boolean, list of string, or object` | People. `true` is everyone, a list is those logins, `false` is no one, and an object sets `include` and `exclude`. Default `true`. |
| `trigger.linear_issue.for.users.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.linear_issue.for.users.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.linear_issue.for.bots` | `boolean, list of string, or object` | Bots. `true` is every bot, a list is those logins, `false` is none, and an object sets `include` and `exclude`. Default `false`. |
| `trigger.linear_issue.for.bots.include` | `boolean or list of string` | `true` is everyone, a list is those logins, `false` is no one. Default `true`. |
| `trigger.linear_issue.for.bots.exclude` | `list of string` | Logins to skip, applied after `include`. |
| `trigger.linear_issue.for.teams` | `list of string` | GitHub team slugs of the installed organization, such as `platform` for `@org/platform`. The author must also be on one of them. Bots never are. |
| `trigger.sentry` | `object` | Sentry alerts to match; see [Sentry alerts](/docs/agents/triggers#sentry-alerts). |
| `trigger.sentry.on` | `list of string`, required | Which alerts fire. One of `issue_alert` (An issue alert fires), `metric_alert` (A metric alert becomes critical). |
| `trigger.sentry.projects` | `list of string` | Sentry project slugs. Default every project. |
| `trigger.slack_channel` | `object` | Runs when someone creates a Slack channel; see [Slack channels](/docs/agents/triggers#slack-channels). Takes no fields. |
| `input` | `object` | Typed input for sessions you start from the dashboard, CLI, or API; a trigger fires without it. See [Typed input and output](/docs/agents/schema#typed-input-and-output). |
| `input.json_schema` | `object` | Schema the request's `input` must match. |
| `input.message` | `string` | First message template; omit to send the input as JSON. `{{field}}` inserts a field from the input or the trigger event. |
| `session` | `object`, required | The session each run starts. |
| `session.claude_code` | `object` | Runs the session with [Claude Code](/docs/harnesses/claude). Exactly one of `claude_code` or `codex`. |
| `session.claude_code.prompt` | `string` | The task. |
| `session.claude_code.model` | `string`, required | A Claude Code model from [Models](/docs/models#current-support). |
| `session.claude_code.effort` | `string` | Reasoning effort, where the model supports it. One of `low`, `medium`, `high`, `xhigh`, `max`. |
| `session.claude_code.max_turns` | `integer` | The most turns the agent can take on each message, a whole number above 0. |
| `session.claude_code.fallback_model` | `string` | The model Claude Code switches to when `model` is overloaded. Default none. |
| `session.claude_code.settings` | `object` | A Claude Code `settings.json` in a repository, applied below the settings Ellipsis manages. Hooks stay disabled. |
| `session.claude_code.settings.path` | `string`, required | Path of the file, relative to the repository root. |
| `session.claude_code.settings.repository` | `object` | Where `path` resolves. Default the repository this file lives in. |
| `session.claude_code.settings.repository.name` | `string`, required | The repository name, without the owner. |
| `session.claude_code.settings.repository.owner` | `string` | The GitHub owner. Default your account. |
| `session.claude_code.settings.repository.ref` | `string` | Branch, tag, or commit. Default the default branch. |
| `session.codex` | `object` | Runs the session with [Codex](/docs/harnesses/codex). Exactly one of `claude_code` or `codex`. |
| `session.codex.prompt` | `string` | The task. |
| `session.codex.model` | `string`, required | `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, or `gpt-5.6-sol`. |
| `session.codex.effort` | `string` | Reasoning effort. GPT-6 Astra doesn't accept `none`. Default `medium`. One of `none`, `low`, `medium`, `high`, `xhigh`, `max`. |
| `session.environment` | `string or object` | A saved environment name or an inline environment; see the [Environment schema reference](/docs/environments/schema). Default the built-in basic environment. |
| `session.permissions` | `object` | GitHub and Ellipsis access; see [Permissions](/docs/permissions). |
| `session.permissions.ellipsis` | `boolean or object` | Ellipsis API access for the session token. `true` grants everything a session token can do; a map narrows it per resource. Default `true`. |
| `session.permissions.github` | `object` | GitHub access for the session token. |
| `session.permissions.github.permissions` | `string or object` | `read_only`, or a map of GitHub permission names to levels. Default the installation's full permissions. |
| `session.permissions.github.repositories` | `list of string` | Repositories the token may reach. Default every repository of the installation. |
| `session.budget` | `object` | Spend limits in US dollars. Account limits also apply; see [Budgets](/docs/budgets). |
| `session.budget.session` | `number` | The most one session may spend, in US dollars. |
| `session.budget.day` | `number` | The most the agent may spend over the last day. |
| `session.budget.week` | `number` | The most the agent may spend over the last 7 days. |
| `session.budget.month` | `number` | The most the agent may spend over the last 28 days. |
| `session.conversation` | `object` | How the session behaves after its first turn. |
| `session.conversation.interactive` | `boolean` | Whether the session stays open for follow-ups; `false` runs once. Default `true`. |
| `session.metadata` | `map of string` | String keys and values stored on every session. |
| `session.output` | `object` | Structured result; see [Typed input and output](/docs/agents/schema#typed-input-and-output). |
| `session.output.json_schema` | `object`, required | Schema for the final JSON result. Read it from `GET /v1/sessions/{session_id}/output`. |
| `session.skills` | `list of object` | Skills from a repository, for Codex; see [Add a Codex skill](/docs/agents/schema#add-a-codex-skill). Up to 10. Default none. |
| `session.skills[].path` | `string`, required | Directory holding `SKILL.md`, relative to the repository root. |
| `session.skills[].repository` | `object` | Where `path` resolves. Default the repository this file lives in. |
| `session.skills[].repository.name` | `string`, required | The repository name, without the owner. |
| `session.skills[].repository.owner` | `string` | The GitHub owner. Default your account. |
| `session.skills[].repository.ref` | `string` | Branch, tag, or commit. Default the default branch. |

In `input.message`, `{{field}}` inserts a field from the input. Triggered agents can insert event fields the same way. Every reference must exist in the input schema or the trigger's event, and in both when the agent has a trigger and a schema.

See [Models](/docs/models), the [Environment schema reference](/docs/environments/schema), and [Permissions](/docs/permissions).
