Plane docs
Concepts

Agents and versions

A versioned JSON document, and what appending a version means.

An agent is a document. agents.upsert writes it; every change appends a version and a session is pinned to the version it started on, for life. So a prompt edit cannot retroactively change what a conversation from last week was doing, and a transcript is always explicable by a definition that still exists.

await client.agents.upsert({
  slug: "support-bot",
  definition: {
    name: "Support bot",
    description: "Answers order questions and opens returns.",
    model: {
      provider: "gateway",                       // a provider *name*, not a type
      model: "anthropic/claude-sonnet-4.6",
      reasoning: "low",
      fallback: { models: [{ model: "openai/gpt-5.6-luna" }] }
    },
    system: "You are a support agent. Look things up before answering.",
    tools: [
      { type: "mcp", connection: "support-bot", allow: ["lookup_order", "create_return"] }
    ],
    policies: {
      toolCalls: { default: "allow", byTool: { create_return: "hook" } },
      decisions: { expiresInSeconds: 3600, onExpire: "deny" }
    },
    limits: { maxTurns: 12 },
    overrides: { model: false, reasoning: true, system: "append" }
  }
})

Every field but name, model and system is optional, and every policy object is complete when empty: "policies": {} is a valid permissive policy, "compaction": {} is a valid compaction policy. Defaults live in one place and are documented on the page for each feature.

The fields

FieldWhat it decides
modelProvider name, model id, reasoning level, and a fallback ladder.
systemThe system prompt.
toolsWhat it may call: MCP connections, inline servers, provider-executed tools, sandbox tools.
toolLoadingeager / deferred / auto — see deferred tools.
policiesThe tool-call gate and what happens to an unanswered approval.
limitsmaxTurns, token budgets per session.
compactionWhen and how the transcript is compacted.
overridesWhich axes a session may vary — see overrides.
subagents, subagentPolicyThe sub-agent roster and its caps.
sandboxA machine of its own: image, resources, network, lifecycle, tool narrowing.
identityWhether a session must assert an identity, and which scopes.

Versions

await client.agents.get({ id: agentId })              // the latest definition
await client.agents.versions({ id: agentId })         // the history
await client.agents.getVersion({ id: agentId, version: 3 })

// Optimistic concurrency: refuses with VersionConflict if somebody else moved it.
await client.agents.upsert({ slug, definition, expectedVersion: 7 })

An agent with a sandbox has an image, and an image has to be baked. The bake is not part of the write: upsert returns as soon as the version is stored and Plane builds behind it. Starting a session on a version whose image is not ready is AgentNotReady, carrying baking or failed.

let { status } = await client.agents.imageStatus({ id: agentId })
while (status === "baking") {
  await new Promise((r) => setTimeout(r, 5_000))
  ;({ status } = await client.agents.imageStatus({ id: agentId }))
}

imageStatus is a read and never starts a build, so polling it is free. agents.rebake rebuilds the latest version's image without appending a version — for when the image's content moves while its spec does not (a git clone step bakes whatever main was that morning).

Reasoning

One vocabulary — none, minimal, low, medium, high, max — mapped per adapter. A model that cannot express the level gets the option omitted, never approximated: a thinking budget you did not ask for is a silent bill. The level actually in force is recorded on model.request.started.

Fallback

A ladder, tried in order, and only for the failure reasons you list.

model: {
  provider: "gateway",
  model: "anthropic/claude-sonnet-4.6",
  fallback: {
    models: [{ model: "openai/gpt-5.6-luna" }, { provider: "anthropic", model: "claude-haiku-4.6" }],
    // the default
    on: ["unavailable", "rate_limited", "timeout", "server_error"]
  }
}

A rung inherits the primary's provider and reasoning level unless it states its own. The same-model retry runs inside each attempt, so a fallback happens only once backing off has given up. A failure Plane cannot classify has no reason and fails the turn rather than falling down the ladder — the list is an opt-in, and silently paying twice to hide a bug is worse than an error.

Each rung gets its own model.request.started / model.request.failed pair, so a dead attempt's tokens never mix into the answer you render.

Promotion

Moving an agent between environments is agents.promote. It rewrites the names the definition references and resolves every one of them in the target, refusing as a whole if anything is missing.

On this page