Claude Platform Docs

Manage resources as code with ant apply

Declare agents, environments, skills, memory stores, deployments, and vaults as files in your repository and keep the API’s resources in sync with them using ant apply.

ant apply creates and updates Claude API resources from files: agents, environments, skills, memory stores, deployments, and vaults. They live in your repository and change through the same review as your code. You describe each resource in a file, run ant apply, and approve the plan it shows. Then you commit the claude-lock.json it writes, so the next run updates the same resources instead of creating new ones.

To install and authenticate the CLI, see the CLI quickstart. ant apply requires CLI version 1.30.0 or later.

Apply your first agent

Write the agent as a Markdown file under agents/ and apply it:

CLI
ant apply agents/summarizer.md
agents/summarizer.md
---
name: Summarizer
model: claude-opus-5-5
---

You are a helpful assistant that writes concise summaries.

The frontmatter holds the agent’s configuration (the fields from Define your agent) and the body is its system prompt. ant apply infers that the file is an agent from its path, here the agents/ directory.

In an interactive terminal, ant apply prints the plan and waits for your approval:

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

Resources will be created with
  credentials   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

Answer d to see details first: the fields of each new resource, or a field-by-field diff of each update. --dry-run prints that detailed plan and exits without changing anything.

To change the agent, edit the file and run ant apply again. The plan then shows an update instead of a create.

Commit claude-lock.json

The first ant apply writes claude-lock.json, the lockfile, in the directory you run it from, so run it from the repository root. It records the ID of the resource each file created and the organization and workspace the resources live in:

claude-lock.json
{
  "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 it with your files. It’s how the next run, on your machine or in CI, finds these resources instead of creating them again, and it’s where you read an agent’s ID to start a session. The two hashes fingerprint what was last sent and what the API returned. That’s how a later run detects an edited file, or a resource changed outside these files.

Grow it into a project

You can declaratively define the other resources as files as well. A file holds the request body you would send to that kind’s create endpoint:

  • An environment is a YAML file in environments/.
  • A memory store is a YAML file in memory_stores/.
  • A deployment is a Markdown file in deployments/: the frontmatter is the request body and the prose becomes the message that starts each session.
  • A skill is a directory with a SKILL.md at its root, conventionally under skills/, uploaded as one bundle.
  • A vault is a YAML file in vaults/ whose only fields are display_name and, optionally, metadata. Vault files need CLI version 1.34.0 or later.

Any resource except a skill can be written as YAML, JSON, or Markdown. In Markdown, the frontmatter is the body and the prose fills the kind’s text field: an agent’s system, an environment’s or memory store’s description, a deployment’s first message. A vault has no text field, so ant apply refuses a vault Markdown file that has prose.

Resources refer to each other by path. Wherever the API expects another resource’s ID, write the relative path to that resource’s file instead. In this project, the reviewer agent lists ../skills/pr-summary under skills, the agent in agents/lead.md lists ./reviewer.md under multiagent.subagents.predefined_agents, and the deployment names its agent, environment, and memory store by path. ant apply creates them in dependency order and fills in the real IDs. A path under multiagent.subagents.predefined_agents or multiagent.workflows.predefined_agents needs CLI version 1.40.0 or later. The project has six files:

---
name: Code reviewer
model: claude-opus-5-5
tools:
  - type: agent_toolset_20260401
    configs:
      - name: web_search
        enabled: false
      - name: web_fetch
        enabled: false
skills:
  - ../skills/pr-summary
---

You review pull requests for correctness, security, and readability.

Apply the whole directory:

CLI
ant apply .

claude-lock.json then has an entry for every file in the project.

Relative paths are how these files point at each other. ant apply pins agent and skill references to the version it just applied, so editing reviewer.md or the skill updates everything that references them in the same run. A path also works inside an object, as in the deployment’s resources entry, where the other keys such as access are kept.

To point at a resource these files don’t manage, write its ID (agent_..., skill_...) instead. Anything else, such as {type: anthropic, skill_id: xlsx}, is sent to the API as written. A skill reference can also be a GitHub URL of the form https://github.com/<owner>/<repo>/tree/<branch>/<dir>, for example a directory of Anthropic’s open-source skills repository: ant apply downloads and uploads that directory, pinned to the resolved commit until you run with --upgrade (set GITHUB_TOKEN for a private repository).

A path can also be a glob. Under skills, a glob references every skill it matches and skips anything else it matches, such as a README. Under an agent’s tools, a path or glob names YAML or JSON files instead of resources: ant apply inserts each file’s contents, one tool or a list of tools, into the list at that position. Put those files in a subdirectory such as agents/tools/, because ant apply refuses to insert a file that it would also read as an agent. A path or glob that matches nothing is an error.

agents/reviewer.md
---
name: Code reviewer
model: claude-opus-5-5
tools:
  - type: agent_toolset_20260401
    configs:
      - name: web_search
        enabled: false
      - name: web_fetch
        enabled: false
  - ./tools/*.json # the tools in these files are inserted here
skills:
  - ../skills/* # every skill under skills/
---

You review pull requests for correctness, security, and readability.
agents/tools/get_ci_status.json
{
  "type": "custom",
  "name": "get_ci_status",
  "description": "Get the CI status of a pull request",
  "input_schema": {
    "type": "object",
    "properties": {
      "pull_request": {
        "type": "integer",
        "description": "Pull request number"
      }
    },
    "required": ["pull_request"]
  }
}

How ant apply infers a file’s kind

When ant apply walks a directory, it determines each file’s kind from the first of these that matches:

  1. A top-level type field in the file.
  2. The directory the file is directly in: agents/, environments/, memory_stores/, deployments/, or vaults/.
  3. A file name that starts with the kind, such as environment_staging.md.
    • Vaults are the exception: ant apply does not take the kind from a name such as vault_staging.yaml, because other tools name their secrets files vault.yml. Keep a vault file in vaults/ or give it type: vault.

It skips files that match none of these, such as READMEs and CI configuration, unless you name them on the command line. A named Markdown file that matches none is treated as an agent, and a named YAML or JSON file that matches none is an error.

Edit and reapply

Running ant apply with no arguments reconciles every file the lockfile tracks. At a terminal, it also lists untracked resource files under the lockfile’s directory and offers to add them. Deleting a field from a file clears it on the resource if the API allows that field to be cleared. A field you never set, or one the API can’t clear, keeps its current value.

If a resource was edited, archived, or deleted outside these files (in the Claude Console, for example), the plan ends with This plan cannot be applied: and the reason. The command then exits with refusing to apply. Pass --force to overwrite the edit or create a replacement.

Deleting a file leaves its resource in place with a warning, and --prune removes it (archiving it, or deleting it for a skill). Renaming a file therefore declares a new resource and leaves the old one in place until you prune.

ant apply can’t adopt a resource you created in the Console or with ant beta:agents create. Only what’s in the lockfile is managed, and applying a file that describes an existing agent creates a second one. If you downloaded your agent from the Console with Export as code, the download includes its own claude-lock.json, so applying it updates the resources you built there.

Run ant apply in CI

Without a terminal, ant apply prints the plan and stops with cannot ask for confirmation without a terminal; re-run with --yes to apply, or --dry-run to see the plan only. Set up CI as follows:

  • Run ant apply --yes . on your default branch after merge, naming the project directory. A bare ant apply --yes reconciles only files the lockfile already tracks and skips a newly added one.
  • On pull requests, run ant apply --dry-run . to print the plan for reviewers. It’s informational only and exits 0 even when the plan is blocked.
  • Commit the updated claude-lock.json at the end of the job, even when the apply step failed partway, because a partial apply still records what it created.
  • Run one apply at a time, because nothing locks the lockfile.
  • Authenticate with Workload Identity Federation rather than a stored API key, as an identity that reaches the organization and workspace recorded in claude-lock.json. ant apply refuses credentials that resolve to any other organization or workspace.

This GitHub Actions workflow does all of that except the pull request plan. It applies on every push to main:

.github/workflows/ant-apply.yml
name: ant apply
on:
  push:
    branches: [main]
    paths-ignore: [claude-lock.json] # so the lockfile commit doesn't start this workflow again

jobs:
  apply:
    runs-on: ubuntu-latest
    environment: agents-prod # protect it in GitHub: required reviewers, main only
    concurrency: ant-apply # one apply at a time
    permissions:
      id-token: write # to request the identity token
      contents: write # to push the lockfile commit
    env:
      ANTHROPIC_FEDERATION_RULE_ID: fdrl_...
      ANTHROPIC_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
      ANTHROPIC_SERVICE_ACCOUNT_ID: svac_...
      ANTHROPIC_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
    steps:
      - uses: actions/checkout@v5
        with:
          ref: main # the tip of main, so a queued run has the previous run's lockfile commit
      - uses: anthropics/setup-ant@v1 # installs the ant CLI
      - name: Fetch GitHub OIDC token
        run: |
          curl -fsS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
            | jq -r .value > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
      - name: Apply
        run: ant apply --yes .
      - name: Commit the lockfile
        if: always() # a partial apply still records what it created
        run: |
          git diff --quiet claude-lock.json && exit 0 # nothing changed
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git commit -am "Update claude-lock.json [skip ci]"
          git pull --rebase # main can move during the apply, and a plain push would then be rejected
          git push

Run the first ant apply on your machine and commit claude-lock.json before you turn the workflow on. The last step commits changes only to a lockfile that Git already tracks.

To create the federation rule and service account that the env block names, see Use WIF with GitHub Actions. Enable the rule for a single workspace, the one recorded in claude-lock.json, so that the token exchange doesn’t need a workspace ID. Because the job names an environment, its identity token’s sub claim is repo:<owner>/<repo>:environment:agents-prod, so that’s the value the rule’s subject_prefix must match.

Flags

FlagEffect
--dry-runPrint the plan and exit without applying or writing the lockfile. Exits 0 even when the plan is blocked.
--yesApply without asking for confirmation. Required when there’s no terminal.
--forceApply even where a resource was changed, archived, or deleted outside these files.
--pruneRemove resources that are in the lockfile but no longer declared in a file.
--upgradeRe-resolve skills referenced by GitHub URL, which otherwise stay pinned to the commit recorded in the lockfile.
--lock-file <path>Use this lockfile instead of searching upward from the current directory. Keep one for each organization or workspace: ant apply refuses a lockfile whose organization or workspace doesn’t match your credentials.
--verbose, -vShow unchanged resources and full field values in the plan.

Next steps

Run the agents you applied, from the CLI or an SDK

Deployment fields, run history, and pausing

Scripting patterns and use from Claude Code

Was this page helpful?