ai-ml

Claude Managed Agents: ant apply Tutorial (2026)

September 17, 2026

Claude Managed Agents: ant apply Tutorial (2026)

On September 3, 2026, Anthropic's ant command-line tool picked up a new subcommand: ant apply. It reads agent, environment, skill, memory store, and deployment definitions out of files in your repository and syncs them to the Claude API — the same plan-then-apply workflow Terraform made standard for cloud infrastructure, now pointed at Claude Managed Agents resources instead of servers12.

TL;DR

ant apply <path> creates or updates Claude Managed Agents resources from Markdown, YAML, or JSON files, shows you a plan, and writes a claude-lock.json on approval so the next run updates the same resources instead of duplicating them. It requires CLI version 1.30.0 or later, shipped September 3, 2026, and runs in CI with --yes or --dry-run12.

What you'll learn

  • How to declare a Claude Managed Agent, environment, memory store, and scheduled deployment as files in one repository, and turn them into real API resources with ant apply
  • How ant apply decides a file's kind from its path, and the two different ways a misnamed file gets punished for it — one silently, one with a hard error
  • What's inside claude-lock.json, and why you commit it instead of ignoring it
  • Two gotchas buried in Anthropic's own CI guidance that would quietly break a copy-pasted GitHub Actions setup
  • How to authenticate a CI job against the Claude API with Workload Identity Federation instead of a stored key
  • What this tutorial's file examples were checked against, given no live API key was available to run them for real

Two CLIs, one letter apart

ant is not claude. The ant CLI is Anthropic's command-line client for the Claude API itself — every API resource, including Managed Agents, exposed as a subcommand3. claude is the separate Claude Code CLI for agentic coding in a terminal.

The two are meant to work together. Claude Code can shell out to ant directly, parse its structured output, and act on your API resources without any custom integration code, as in "list my recent agent sessions and summarize which ones errored"4.

This post is about ant, and specifically its apply subcommand.

Prerequisites

  • The ant CLI, version 1.30.0 or later. Install with brew install anthropics/tap/ant on macOS, the documented curl installer for Linux and WSL, or go install from source3.
  • ant --version to confirm you're above 1.30.0. As of this tutorial, the current release is 1.33.0 (September 15, 2026), and nothing between 1.30.0 and 1.33.0 changed apply's behavior or flags25.
  • ant auth login for an interactive OAuth session, or Workload Identity Federation for CI — covered near the end of this post36.
  • An ANTHROPIC_API_KEY only if you want to run any of this against a real workspace. See Verification below for what this tutorial's examples were, and weren't, checked against.

Apply your first agent

An agent is Anthropic's term for a reusable, versioned configuration: model, system prompt, tools, and skills bundled together and referenced by ID each time you start a session7. Write one as a Markdown file under agents/, then apply it:

ant apply agents/summarizer.md
---
name: Summarizer
model: claude-opus-5
tools:
  - type: agent_toolset_20260401
---

You are a helpful assistant that writes concise summaries.

The frontmatter holds the agent's configuration fields — the same fields you'd send to the agent create endpoint — and the prose body becomes its system prompt. ant apply infers that this file is an agent because it lives under agents/1.

In an interactive terminal, ant apply prints a plan and waits:

First apply  ./claude-lock.json does not exist yet and will be created

Resources will be created with
  credentials   API key (--api-key / ANTHROPIC_API_KEY)
  host          api.anthropic.com
  organization  1b0c2a4d-6c1f-4f0e-9a57-2e8d1c3b4a5f
  workspace     wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ

Preview  ./claude-lock.json (new)

± Name                    Plan
+ ./agents/summarizer.md  create

Resources  + 1 to create

Apply these changes? (y)es / (n)o / (d)etails y

Apply  ./claude-lock.json

± Name                    Status
+ ./agents/summarizer.md  created    agent_011CYm1BLqPXpQRk5khsSXrs

Resources  + 1 created

State written to ./claude-lock.json

Answering d shows the full field-by-field detail before you commit to it. --dry-run prints that same detailed plan and exits without changing anything or writing a lockfile1.

What's inside claude-lock.json

The first ant apply writes a lockfile in whatever directory you ran it from — run it from your repository root. It records which file created which resource, and where:

{
  "version": 1,
  "origin": {
    "base_url": "https://api.anthropic.com",
    "organization_id": "1b0c2a4d-6c1f-4f0e-9a57-2e8d1c3b4a5f",
    "workspace_id": "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ"
  },
  "resources": {
    "./agents/summarizer.md": {
      "kind": "agent",
      "id": "agent_011CYm1BLqPXpQRk5khsSXrs",
      "version": "1",
      "hash": "d23251c8d99b3613a64f3f8d87f5fad4",
      "remote_hash": "1b771bee5bdbf600a5ad972fdac32d94"
    }
  }
}

Commit this file with your code. It's how the next run — yours or CI's — finds the same resources instead of creating new ones, and it's where you read a resource's real ID once it exists. The two hashes fingerprint what your file last sent and what the API last returned, which is how a later run can tell an edited file apart from a resource someone changed outside these files entirely1.

Growing it into a project

Anthropic's docs demonstrate this with a pull-request reviewer setup. To keep this concrete and avoid just retyping their example, here's the same mechanics applied to a different problem: a small agent team that drafts and fact-checks blog posts on a schedule — coincidentally close to the kind of pipeline that produced this post. Every field below is a real, documented field; only the scenario is original.

This project skips the fifth kind, skills, on purpose: a skill is a directory with a SKILL.md at its root, uploaded as one bundle, not a single YAML, JSON, or Markdown file like the other four — different enough mechanically that it deserves its own example rather than a token entry here1.

Five files, four kinds:

agents/fact-checker.md
agents/writer.md
environments/sandbox.yaml
memory_stores/style-notes.yaml
deployments/nightly-draft.md

agents/fact-checker.md — a subagent with no roster of its own:

---
name: Fact Checker
model: claude-opus-5
tools:
  - type: agent_toolset_20260401
description: Verifies claims in a draft against live sources before it ships.
---

Check every statistic, date, and named source in the draft you're given.
Flag anything you can't verify instead of guessing at it.

agents/writer.md — the coordinator, delegating to the fact-checker by relative path:

---
name: Draft Writer
model:
  id: claude-opus-5
  effort: high
tools:
  - type: agent_toolset_20260401
multiagent:
  type: coordinator
  agents:
    - ./fact-checker.md
---

Draft a technical blog post about agentic AI, then hand the draft to your
fact-checker subagent before you finish.

A coordinator's multiagent.agents roster is exactly how you list the agents it can delegate to, and ant apply resolves that relative path to the fact-checker's real ID once both files are applied — in dependency order, so the subagent exists before the coordinator references it781.

environments/sandbox.yaml — the cloud sandbox both agents run in:

name: blog-pipeline-sandbox
config:
  type: cloud
  packages:
    pip:
      - pyyaml
  networking:
    type: limited
    allowed_hosts:
      - api.example-cms.com
    allow_package_managers: true

allow_package_managers: true is required whenever packages is set on a limited-networking environment; leaving it off returns a 400 error, even if the package registry's own host happens to be in allowed_hosts9.

memory_stores/style-notes.yaml — cross-session notes the writer checks before drafting:

name: House Style Notes
description: >-
  Corrections and fact-check findings from past drafts, for the writer
  agent to check before drafting a new one.

A memory store's description is what the agent reads to understand what the store is for; it isn't a comment for humans10.

deployments/nightly-draft.md — the cron trigger, wired to the memory store and carrying a per-run spend ceiling:

---
name: Nightly draft run
agent: ../agents/writer.md
environment_id: ../environments/sandbox.yaml
resources:
  - path: ../memory_stores/style-notes.yaml
    access: read_write
schedule:
  type: cron
  expression: "0 6 * * *"
  timezone: UTC
budget:
  type: limit
  max_list_cost:
    amount: "500"
    currency: USD
---

Draft tonight's post and hand it to the fact-checker before you finish.

A deployment written as Markdown works differently from the other kinds: the frontmatter still holds the request body, but instead of a separate initial_events array, the Markdown prose itself is used as the message that opens each session1.

Deployments accept memory store, file, and GitHub resources through the same resources array a session uses, each entry pointing at a file by the identical relative-path rule shown above. This one uses read_write so the writer's drafting session can add its own corrections back to the store, not just read what's already there1110.

The budget field takes the same shape as a session budget and is copied onto every run the deployment starts, so it bounds each run separately rather than acting as a cumulative cap across the whole deployment. A "500" cap here allows spending up to roughly $5 in public list-rate costs on any single night's run, not $5 total across all runs11.

Apply the whole directory in one command:

ant apply .

ant apply creates the environment, the memory store, and the fact-checker first — none of those three files reference anything else — then the writer, since its file references the fact-checker, and the deployment last, since it references the environment, the memory store, and the writer. claude-lock.json ends up with five entries, one per file.

How kind inference actually works

ant apply decides what a file is by checking three things, in this exact order, and stopping at the first match1:

  1. A top-level type field written directly in the file.
  2. The directory the file sits in: agents/, environments/, memory_stores/, or deployments/.
  3. A filename that starts with the kind's name, such as environment_staging.md sitting outside any of those directories.

Two failure modes fall out of this order, and neither one is loud. A file with none of the three loses silently if it's Markdown — the docs are explicit that "a named Markdown file that matches none is treated as an agent." Rename agents/notes.md to docs/notes.md outside any recognized directory and pass it by name on the command line, and ant apply tries to create it as an agent rather than complaining.

The YAML and JSON case is the opposite and louder: the same situation for a .yaml or .json file is a hard error instead of a silent misclassification1. Files that match none of the three rules and aren't named explicitly on the command line — READMEs, CI configs — are skipped outright, which is the safe default for everything that isn't meant to be applied.

Drift detection: when the Console wins the argument

If someone edits, archives, or deletes a resource outside these files — in the Claude Console, for instance — the next ant apply for that file doesn't silently overwrite the change. The plan ends with This plan cannot be applied: and the specific reason, then the command exits with refusing to apply1.

--force overwrites the out-of-band edit, or creates a replacement resource if the original was deleted. Absent --force, ant apply cannot adopt a resource that already exists but wasn't created through these files in the first place — including anything made via the Console or ant beta:agents create directly.

Point a file at an existing agent's configuration without that agent being in the lockfile, and ant apply creates a second, separate agent rather than recognizing the first one as a match1.

Two gotchas Anthropic's own CI section buries

Both of these come straight from the "Run ant apply in CI" section of Anthropic's docs — they're not edge cases found by trial and error, just details easy to skim past on a first read1.

A bare ant apply --yes silently skips new files. Anthropic's documented CI pattern is to always name the project directory — ant apply --yes . — specifically because a bare ant apply --yes with no path argument "reconciles only files the lockfile already tracks and skips a newly added one."

Add a sixth file to the blog-pipeline project above and forget to name the directory in your CI job, and that new resource is never created — with no error, because skipping an untracked file isn't treated as a failure1.

--dry-run reports success even when the real apply would refuse. A --dry-run plan "exits 0 even when the plan is blocked." A pull request whose --dry-run step shows This plan cannot be applied: for a drift conflict still passes CI, because the exit code doesn't reflect that. Reading the printed plan text — not just the exit code — is the only way a PR check catches this before someone merges and the real apply refuses1.

Running it in CI with Workload Identity Federation

Anthropic's own recommendation for CI is to skip stored API keys entirely and authenticate with Workload Identity Federation (WIF): your CI provider issues a short-lived OIDC token, Anthropic validates it against rules you configure once in the Console, and exchanges it for a Claude API token that's typically minutes, not months, from expiring61.

The documented CI pattern is three pieces:

  • On pull requests: ant apply --dry-run ., informational only, to show reviewers the plan.
  • After merge to the default branch: ant apply --yes ..
  • At the end of the job, commit the updated claude-lock.json — even if the apply step failed partway, because a partial apply still records what it did create1.

For GitHub Actions specifically, the identity token comes from the Actions OIDC endpoint with no stored secret at all. WIF's default token lifetime is 3,600 seconds (configurable from 60 to 86,400), and the default OAuth scope it grants — workspace:developer — is the same access level as a workspace API key6.

One documented footgun applies to any WIF migration, not just CI: ANTHROPIC_API_KEY sits above the federation tiers in every SDK's credential-resolution order, so a leftover key in the environment silently wins over federation rather than erroring. ant auth status reports which credential source actually won6.

Flags reference

FlagEffect
--dry-runPrints the plan and exits without applying or writing the lockfile. Exits 0 even when the plan is blocked.
--yesApplies without asking for confirmation. Required when there's no terminal.
--forceApplies even where a resource was changed, archived, or deleted outside these files.
--pruneRemoves resources that are in the lockfile but no longer declared in a file (archives them, or deletes for a skill).
--upgradeRe-resolves skills referenced by a GitHub URL, which otherwise stay pinned to the commit recorded in the lockfile.
--lock-file <path>Uses this lockfile instead of searching upward from the current directory; refuses a lockfile whose org or workspace doesn't match your credentials.
--verbose, -vShows unchanged resources and full field values in the plan.

Source: Anthropic's ant apply documentation, current as of this post1.

ant apply against the alternatives

This is original comparison, not a quote from any single source — each column reflects what's directly documented about that workflow's own mechanics.

Console onlyHand-rolled curl/SDK scriptsant apply
Versioned in gitNo — changes live only in the Console's own historyYes, if you write it that wayYes, by design1
Preview before changing anythingNoOnly if you build a diff yourselfBuilt in (--dry-run, or details at the prompt)1
Detects out-of-band editsN/A — the Console is the source of truthNo, unless you build your own state fileBuilt in, via claude-lock.json1
Multi-resource dependency orderManual — you create each resource in the right order yourselfManualAutomatic, resolved from path references1
Built for CINoWhatever you script--yes / --dry-run, documented GitHub Actions pattern1

Verification

None of the command output or file examples above was captured from a live ant apply run against a real workspace — that requires a paid ANTHROPIC_API_KEY or a configured Workload Identity Federation rule and an existing Claude organization, neither of which was available while writing this tutorial. Nothing here should be read as a captured terminal session.

Instead, every field name, flag, file format rule, and piece of command output was checked against Anthropic's own published documentation for ant apply1, the ant CLI quickstart and scripting guides34, the agent, multiagent-orchestration, environment, memory store, and scheduled-deployment reference pages7891011, and the Workload Identity Federation guide6.

All were fetched directly from platform.claude.com, most on 2026-09-17 and rechecked against a fresh fetch on 2026-09-18 to confirm nothing had drifted; the multiagent-orchestration page was fetched only on that second pass, specifically to independently confirm the coordinator field structure against a source beyond the one worked example. None of it was reconstructed from memory or secondary write-ups.

The CLI's release date and version number were independently cross-checked against the anthropic-cli GitHub repository's own release notes for v1.30.0 and the current v1.33.025, which additionally confirmed that no release since 1.30.0 has changed apply's flags or behavior. The two "gotchas" quoted above are direct, attributed quotations from Anthropic's own CI guidance, not inferences.

The five example files in the "Growing it into a project" section use only field names confirmed on the cited reference pages; the scenario connecting them is this post's own, not copied from Anthropic's PR-reviewer example. Every YAML and Markdown-frontmatter block in this post, including the claude-lock.json JSON, was parsed with a real YAML/JSON parser (Python's pyyaml and json modules) to confirm it's syntactically valid — not just visually plausible.

The bottom line

ant apply brings a plan-then-apply workflow to Claude Managed Agents resources: describe an agent, environment, memory store, or deployment as a file, preview the exact change, and let a committed lockfile keep runs idempotent.

As of this post, it's about two weeks old. The two behaviors most likely to bite a copy-pasted setup — a bare ant apply --yes skipping new files, and --dry-run reporting success even on a blocked plan — are both spelled out in Anthropic's own docs, just easy to miss on a first pass through them.

Footnotes

  1. Anthropic, "Manage resources as code with ant apply" — https://platform.claude.com/docs/en/cli-sdks-libraries/cli/apply (file formats, kind inference rules, claude-lock.json schema, drift detection, CI guidance including the --yes-skips-new-files and --dry-run-exits-0 behaviors, flags table; fetched 2026-09-17) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

  2. GitHub, anthropics/anthropic-cli release v1.30.0 — https://github.com/anthropics/anthropic-cli/releases/tag/v1.30.0 ("1.30.0 (2026-09-03)... add ant apply for managing agents, skills, environments, memory stores and deployments as code"; fetched 2026-09-17) 2 3 4 5

  3. Anthropic, "CLI quickstart" — https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart (install methods, ant --version, ant auth login; fetched 2026-09-17) 2 3 4 5

  4. Anthropic, "CLI scripting and automation" — https://platform.claude.com/docs/en/cli-sdks-libraries/cli/scripting (Claude Code shelling out to ant, session-driving commands, ant auth print-credentials; fetched 2026-09-17) 2 3

  5. GitHub, anthropics/anthropic-cli release v1.33.0 — https://github.com/anthropics/anthropic-cli/releases/tag/v1.33.0 ("1.33.0 (2026-09-15)"; current latest release as of this post, changelog contains no changes to apply; fetched 2026-09-17) 2

  6. Anthropic, "Workload Identity Federation" — https://platform.claude.com/docs/en/manage-claude/workload-identity-federation (WIF concepts, default token lifetime and scope, credential precedence, ANTHROPIC_API_KEY shadowing behavior; fetched 2026-09-17) 2 3 4 5 6

  7. Anthropic, "Define your agent" — https://platform.claude.com/docs/en/managed-agents/agent-setup (agent configuration fields, multiagent.agents roster field; fetched 2026-09-17) 2 3

  8. Anthropic, "Multiagent orchestration" — https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration (confirms multiagent.type: coordinator alongside agents in every shown coordinator example; fetched 2026-09-18) 2

  9. Anthropic, "Cloud environment setup" — https://platform.claude.com/docs/en/managed-agents/environments (environment fields, packages, networking.allow_package_managers requirement; fetched 2026-09-17) 2

  10. Anthropic, "Using agent memory" — https://platform.claude.com/docs/en/managed-agents/memory (memory store name/description fields; fetched 2026-09-17) 2 3

  11. Anthropic, "Scheduled deployments" — https://platform.claude.com/docs/en/managed-agents/scheduled-deployments (deployment fields, cron/timezone semantics, per-run budget behavior, resources array; fetched 2026-09-17) 2 3

Frequently Asked Questions

A subcommand of Anthropic's ant CLI that creates and updates Claude Managed Agents resources — agents, environments, memory stores, and deployments — from files in your repository, using a plan-and-apply workflow with a committed lockfile 1 .