---
title: Define an agent
summary: agent(), context, output, filesystem, and subagents as one frozen definition.
canonical: https://docs.caveman.so/docs/agent-sdk/define
license: MIT
capability: agent-sdk
updated: 2026-08-30T15:03:57+02:00
basis: inferred
---

# Define an agent

> agent(), context, output, filesystem, and subagents as one frozen definition.
<DocSchema slug="agent-sdk/define" />

An agent is one frozen `AgentDefinition`. `agent()` is the constructor. Everything downstream, sandbox, Context IR, build, receipts, sees that object. There is no second hidden definition.

```ts

  id: "support",
  instructions: "Answer from policy. Never invent policy.",
  model: auto(),
});
```

`id` must match `^[a-z0-9][a-z0-9_-]{0,95}$`. Duplicate tool names fail at construction. Tool names starting with `cave_` are reserved and also fail at construction.

Default sandbox is `required`. Default reasoning is `low`. Options: `off`, `minimal`, `low`, `medium`, `high`.

## Model selection

`model` is one of: `auto()`, a `provider/model` string, or a Pi model object.

`auto()` resolves in this order: `CAVE_MODEL`, then `.caveman/provider.json`, then the baseline model for the sole supported credential in the environment.

| Credential | Baseline if it is the only one |
|---|---|
| `ANTHROPIC_API_KEY` | `anthropic/claude-haiku-4-5` |
| `OPENAI_API_KEY` | `openai/gpt-5.4-mini` |
| `GEMINI_API_KEY` or `GOOGLE_API_KEY` | `google/gemini-2.5-flash` |

Zero credentials fail. Several credentials and no `CAVE_MODEL` fail. A pinned string must use `provider/model` form. `auto()` never classifies the task or picks between models on quality.

## Context and output

```ts

const playbook = context({
  id: "support.playbook",
  kind: "skill",
  source: file("./support.md"),
  stability: "build",
  safety: "S0",
  priority: "required",
});

const answer = output({
  maxTokens: 500,
  schema: schema.object({ answer: schema.string() }),
});
```

`context()` labels a segment. Kind, stability, safety class, priority, recovery, cache region, and privacy class are all explicit. Defaults: safety `S0`, priority `required`, recovery `none`, privacy `local_sensitive`, cache region `frozen_prefix` when stability is `build` and `live_zone` otherwise.

Build-stable context enters the frozen prefix. Session and turn context stay live. Volatile data in the stable cache zone is rejected. On drift or transform failure the runtime fails open to the original provider-visible bytes.

`output({ maxTokens })` is a token budget on the answer. An optional schema constrains the shape.

## Filesystem layout

`loadAgentDir(rootDir)` lowers a convention directory into an ordinary `agent()` call.

```text
support-bot/
├── instructions.md
├── agent.ts
├── skills/
│   ├── refund-policy.md
│   └── shipping-claims.md
├── tools/
│   └── lookup_order.ts
├── subagents/
└── evals/
```

`agent.ts` default-exports an `AgentDirConfig`: model, optional budget, breakers, and extra context. Instructions come from `instructions.md`. Each `tools/*.ts` default-exports a `tool()`. Each `skills/<name>.md` contributes a short description to one build-stable skill segment; the body is served later by `cave_skill` as a live-zone tool result. The model picks from descriptions. There is no embedding ranker.

The loader writes a generated module entry at `.caveman/agent-dir-entry.mjs`. Directory-loaded runs print a receipt by default, because stdout is yours. `run()` from a hand-built definition does not, because stdout may be a protocol channel.

## Subagents

```ts

const researcher = agent({
  id: "researcher",
  instructions: "Search, then return sources.",
  model: auto(),
});

const root = agent({
  id: "support",
  instructions: "Delegate research. Answer from policy.",
  model: auto(),
  tools: [
    subagent({
      name: "research",
      description: "Look up one question.",
      agent: researcher,
    }),
  ],
});
```

Defaults: `maxInputChars` 32,768, `maxCalls` 1, `maxCostUsd` 1, `maxContextTokens` 128,000. Under a USD-metered parent the child's wallet is carved from the remaining budget and unused remainder returns. A token-metered parent cannot fund a child that declares no `maxTokens`. A host-mode child is refused under a `sandbox: "required"` parent.

## Optional workspace files

The core runtime does not search the repository. Products that want `AGENTS.md`, Agent Skills, Agent Plugins v1, or Vercel OpenPlugin can opt in:

```ts
  applyAgentEnvironment,
  loadAgentEnvironment,
} from "@caveman-ai/agent/plugins";

const environment = await loadAgentEnvironment({ cwd: process.cwd() });
const reviewer = applyAgentEnvironment(agent({
  id: "reviewer",
  instructions: "Review requested changes.",
  model: "openai/gpt-5.4",
  sandbox: "host",
}), environment);
```

Metadata enters the stable prefix. Full skill bodies and command markdown enter only after activation. Plugin MCP, hooks, custom agents, and plugin subprocesses are reported and not launched. Ambient secrets are not inherited.

## What it will not do

`agent()` does not start a process, open a network connection, or look for files. Shared-memory provenance is refused here, at construction, not after a model call. A definition with `sandbox: "host"` cannot produce a Cave Build lock.
