Manifest Reference

Published

Updated

The Agent Environment Spec (AES) manifest is a declarative definition of a VM environment. It specifies the platform, source repositories, setup commands, services, health checks, commands, and agent configuration. Public API VMs boot from environments; store the manifest on an environment, then create VMs or sessions from that environment.

Wallfacer uses manifests at every level: base images, environment snapshots, and session boot all derive from the same format.

JSON Schema

The manifest is formally described by a JSON Schema (Draft 2020-12). The public API validates every submitted manifest against it, then applies additional API checks such as environment permissions, snapshot state, and missing secret references.

Stable URL

https://wallfacer.dev/schemas/aes/v1.json

This URL is permanent for the lifetime of version 1. Backwards-compatible additions edit the file in place. Breaking changes publish at a new URL (v2.json, and so on).

Editor integration

Point your editor at the schema URL and you get completion, validation, and inline docs while you write the manifest.

VS Code YAML (via the Red Hat YAML extension), .vscode/settings.json:

{
  "yaml.schemas": {
    "https://wallfacer.dev/schemas/aes/v1.json": [
      "agent-env.yml",
      "agent-env.yaml"
    ]
  }
}

VS Code JSON (.vscode/settings.json):

{
  "json.schemas": [
    {
      "fileMatch": ["agent-env.json"],
      "url": "https://wallfacer.dev/schemas/aes/v1.json"
    }
  ]
}

JetBrains IDEs support the same schema via Preferences > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings.

Minimal Manifest

The smallest schema-valid manifest specifies the version. In practice, set platform.os explicitly so the environment is portable and obvious:

{
  "version": 1,
  "platform": {
    "os": "linux/amd64"
  }
}

This boots a Linux VM with default resources, no repositories, no setup, and no services beyond the automatically injected agent-api.

Full Schema

version

FieldTypeRequiredDefaultDescription
versionintegeryes--Manifest schema version. Currently 1.

platform

Declares the compute platform and resource requirements.

FieldTypeRequiredDefaultDescription
osstringnoserver defaultTarget OS. linux/amd64 or darwin/arm64. Set this explicitly for predictable environments.
memoryintegerno8192Memory in MB. Set automatically by the server; cannot be overridden by users.
vcpusintegerno2Virtual CPU count. Set automatically by the server; cannot be overridden by users.
gpubooleannofalseReserve GPU access. Set automatically by the server; cannot be overridden by users.
platform:
  os: darwin/arm64

image

Optional base image and OS tuning.

FieldTypeRequiredDefaultDescription
basestringnoplatform defaultBase image identifier, including a version tag.
system.file_descriptor_limitintegernobase image defaultMax open file descriptors per process.
system.spotlightbooleanno--Disable Spotlight indexing. macOS only.
system.tcc.apple_eventsarray of stringsno[]Binaries pre-authorized to send AppleEvents. macOS only.
system.tcc.accessibilityarray of stringsno[]Binaries pre-authorized for accessibility automation. macOS only.
system.localestringnoen_US.UTF-8System locale.

sources

Git repositories to clone into the environment. Always an array, even for a single repo. Each source is cloned during snapshot construction; boot-time source reconciliation is managed by the platform.

The first entry's workspace is the primary workspace (default working directory for commands and Claude Code).

The deprecated source field accepts one source object as shorthand. Prefer sources for new manifests.

FieldTypeRequiredDefaultDescription
repostringyes--Git repository. Short form (owner/repo) or full URL (github.com/owner/repo).
authstringnotokenAuthentication method: token or ssh-key. Credentials are injected at session time, never stored in the manifest.
workspacestringno{root}/{reponame}Absolute path where the repo is cloned. Defaults to /workspace/{reponame} (Linux) or /Users/admin/workspace/{reponame} (macOS).
branchstringnorepository defaultOmit to follow the repository default at snapshot generation, or name a branch to pin it.
clone_strategystringnoshallowshallow (--depth 1 --single-branch, fast) or full (complete history). Use full only if the agent needs git log or blame.
identity.namestringno--Git commit author name.
identity.emailstringno--Git commit author email.
sources:
  - repo: github.com/acme/webapp
    workspace: /Users/admin/workspace
    branch: main
    auth: token
    clone_strategy: shallow
    identity:
      name: Wallfacer Agent
      email: [email protected]

Multi-repo example:

sources:
  - repo: github.com/acme/frontend
    workspace: /workspace/frontend
    branch: main
  - repo: github.com/acme/api
    workspace: /workspace/api
    branch: develop

Each source is cloned into its own workspace path. The first source's workspace is the primary working directory for commands and agent interaction unless a command or service sets working_dir.

When you omit branch, Wallfacer uses the repository default and updates the manifest if that default changes before the next snapshot generation. A branch you name is pinned, even if it currently matches the default. Wallfacer rejects a new or changed pin that is missing with 422 missing_branch. If a pinned branch disappears later, snapshot generation fails with the same error instead of switching branches. The structured response identifies repo, branch, and up to 100 available branches. Its human-readable message lists at most ten names. Choose an existing branch in the manifest. Empty repositories accept any branch.

If a pinned branch disappears after the manifest is saved and a VM reaches the clone step, the clone fails immediately instead of retrying. Its failure reason names the requested branch, repository, and available remote branches; the source clone status records the same reason. Update the pin to an existing branch and regenerate the snapshot.

env

Global environment variables available to all setup commands, services, and named commands.

FieldTypeRequiredDefaultDescription
(key)string----Variable name mapped to its value.

${secrets.KEY} references are valid in any string field anywhere in the manifest and are substituted by the platform before the manifest is delivered to your VM. See Secret references for the full contract, including how missing secrets are rejected.

Setup commands can emit ::set-env name=VALUE on stdout to publish ${VAR} values that persist across subsequent setup commands and services.

env:
  NODE_ENV: development
  DATABASE_URL: postgres://user:${secrets.DB_PASSWORD}@localhost:5432/app
  NPM_TOKEN: ${secrets.NPM_TOKEN}

setup

Sequential commands executed during snapshot construction. Each must complete (exit 0 or pass its healthcheck) before the next runs. Setup commands are baked into the snapshot and not re-run on session restore.

FieldTypeRequiredDefaultDescription
namestringyes--Human-readable label.
runstringyes--Shell command (executed via bash -c).
working_dirstringnofirst source workspaceWorking directory.
envobjectno{}Command-specific env vars (merged with global env).
retriesintegerno2Retry count on failure.
healthcheckobjectno--Verification after command completes. See Health Check Format.
setup:
  - name: Install project dependencies
    run: npm install
    retries: 2
    healthcheck:
      command: test -d node_modules
      retries: 3

  - name: Build project
    run: npm run build

On Linux, setup commands are automatically wrapped with sudo -E bash -c '...' so they have access to package management. On macOS, sudo is not used because Homebrew refuses to run under sudo.

services

Long-running background processes supervised by the in-VM agent. Services are launched concurrently but respect depends_on ordering via topological sort. Each service waits for its dependencies' health checks to pass before starting.

Services do not survive snapshot/restore. They are killed and restarted fresh on every boot.

FieldTypeRequiredDefaultDescription
namestringyes--Unique service name.
runstringyes--Shell command to start the service.
working_dirstringnofirst source workspaceWorking directory.
envobjectno{}Service-specific env vars (merged with global env).
depends_onarray of stringsno[]Service names that must be healthy before this one starts.
portsarray of objectsno[]Ports to expose. See Port Definitions.
healthcheckobjectno--Readiness probe. See Health Check Format.
services:
  - name: database
    run: docker compose up postgres
    healthcheck:
      url: tcp://localhost:5432

  - name: dev-server
    run: npm run dev
    depends_on: [database]
    ports:
      - port: 3000
        name: web
        protocol: http
    healthcheck:
      url: http://localhost:3000/health

commands

Named, reusable command blocks. Commands can be invoked by the platform in these ways:

  1. Lifecycle -- via the ready array (runs after services are healthy, every boot)
  2. HTTP API -- via the in-VM command API
  3. Platform tooling -- for diagnostics and infrastructure operations

Every command execution fires webhook events (command.started, command.completed, command.failed).

FieldTypeRequiredDefaultDescription
(key)stringyes--Command name (the YAML/JSON key). Must be unique. Used in the ready array and API calls.
runstringyes--Shell command (executed via bash -c).
working_dirstringnofirst source workspaceWorking directory.
envobjectno{}Command-specific env vars (merged with global env).
timeoutdurationno30mMaximum execution time.
retriesintegerno1Retry count on failure.
commands:
  migrate:
    run: npm run db:migrate
    working_dir: /workspace
  seed:
    run: npm run db:seed
    working_dir: /workspace
  reset-db:
    run: npm run db:reset && npm run db:seed
    working_dir: /workspace
    timeout: 5m

ready

An array of command names from the commands block. These run sequentially after all services are healthy, on every boot (both fresh build and snapshot restore).

Use ready for idempotent operations that depend on running services: database migrations, cache clearing, dependency updates that catch drift from code changes since the snapshot was built.

commands:
  migrate:
    run: npm run db:migrate
    working_dir: /workspace
  seed:
    run: npm run db:seed
    working_dir: /workspace

ready: [migrate, seed]

Heavy installation belongs in setup (baked into the snapshot). The ready array is for lightweight operations that should re-run every session.

simulators

iOS/Android simulator configuration. Only valid when platform.os is darwin/arm64. Each entry boots a simulator, starts a capture service, and optionally registers build/run commands.

FieldTypeRequiredDefaultDescription
platformstringyes--Simulator platform: ios (future: android).
devicestringnoiPhone 18 ProDevice name. Defaults to the configured device when omitted.
runtimestringnolatestiOS version or latest. A selected version is preserved when the environment is saved.
bundle_idstringno--App bundle ID. Used to verify the app launched in the simulator. Exposed as $IOS_BUNDLE_ID.
buildstringno--Compile, install, and launch script (Swift, native). Generates a build-ios command.
runstringno--Start a dev server pointed at the simulator (Expo). Generates a run-ios command.

Set runtime: latest to select the newest installed iOS runtime that supports the selected device. For example, if a newer runtime supports only another device, Wallfacer uses the newest compatible runtime instead. The stored manifest keeps latest, so an image with newer compatible runtimes can use them without changing the manifest. If no installed runtime supports the device, simulator startup fails with an error naming the device and installed runtime versions. Set a specific version to pin it; the simulator fails to start if that runtime is not installed or does not support the device. The platform fills in omitted values without replacing selections you provide.

The default device is iPhone 18 Pro on the Xcode 27.1 macOS image. A deployment with an IOS_SIMULATOR_DEVICE override uses that configured device instead. Existing saved iOS simulator entries naming iPhone 17 Pro are updated to the configured device, including entries that stored the former default. Entries naming another device and non-iOS entries keep their device selections. A macOS VM still using the Xcode 26.5 image cannot create the new default device; reprovision it onto the Xcode 27.1 image before booting the simulator.

The app's runtime picker currently offers only 26.4. To use latest or another installed runtime, edit the environment manifest directly. Selecting 26.4 fails when the current macOS image does not include it.

What the platform does with each entry:

  1. Boots the simulator via xcrun simctl
  2. Starts SimulatorCapture as a supervised service on port 8000
  3. Exposes $IOS_SIMULATOR_UDID and $IOS_BUNDLE_ID as env vars
  4. If build is set, registers a build-ios command (re-triggerable mid-session)
  5. If run is set, registers a run-ios command
  6. If bundle_id is set, waits for the app to appear in the simulator (2-minute timeout)
  7. Registers the simulator MCP server in Claude Code settings
# Swift project (compile + install + launch):
simulators:
  - platform: ios
    bundle_id: com.acme.app
    build: |
      xcodebuild -project App.xcodeproj -scheme App \
        -destination "id=$IOS_SIMULATOR_UDID" build
      xcrun simctl install $IOS_SIMULATOR_UDID \
        .build/Build/Products/Debug-iphonesimulator/App.app
      xcrun simctl launch $IOS_SIMULATOR_UDID com.acme.app
# Expo project (dev server pointed at simulator):
simulators:
  - platform: ios
    bundle_id: com.acme.app
    run: npx expo run:ios --device $IOS_SIMULATOR_UDID --no-bundler

iOS simulator and its commands run asynchronously after the VM is declared ready, so they do not block session start.

ios

Deprecated single-simulator block kept for older manifests. Prefer simulators for new manifests.

FieldTypeRequiredDefaultDescription
devicestringnoserver defaultDevice model name.
runtimestringnoserver defaultiOS runtime version.
headlessbooleannotrueRun the simulator in headless mode.
bundle_idstringno--App bundle identifier.
buildstringno--Build, install, and launch script.
timeoutdurationno30mMax build duration.
portsarrayno[]Ports exposed by the simulator capture service.

agent

Configures Claude Code inside the VM: the instruction files it loads.

FieldTypeRequiredDefaultDescription
instructionsarray of stringsno[]Paths to instruction files relative to the workspace (e.g. .claude/CLAUDE.md). Written to Claude Code's settings.

Claude Code loads the tool definitions from these servers on demand. It starts a turn knowing the tool names and fetches a tool's full schema before calling it, instead of carrying every schema in context from the first message. An environment that registers several MCP servers therefore leaves more of the context window for the work. The agent still reaches every tool declared here; only the moment the schema is read changes. The platform's own servers load on demand the same way, so nothing you declare here changes how they behave.

agent:
  instructions:
    - .claude/CLAUDE.md
    - docs/ARCHITECTURE.md

MCP servers are not declared here. They are a field on the environment, mcp_servers, edited in the app or through the API, and changing them never regenerates the snapshot. See Environments for the three kinds and Environments and Secrets for the API shape.

When simulators are configured, the platform auto-registers a simulator MCP server pointing to SimulatorCapture.

mounts

S3 buckets attached to the VM's filesystem, so files an agent writes survive the machine. Optional, at most four, and Linux only: a manifest that declares mounts on darwin/arm64 is rejected. See Mounted S3 Storage for how they attach and how uploads are flushed before teardown.

FieldTypeRequiredDefaultDescription
atstringyes--Absolute mountpoint inside the VM. No .. segments, unique per manifest, and under /mnt, /workspace, /app, or /home/appuser without being one of those roots.
bucketstringyes--Bucket name.
credentialsobjectyes--access_key_id and secret_access_key are required; session_token for STS credentials. Use ${secrets.KEY} references.
regionstringconditional--Required unless endpoint is set.
endpointstringnohttps://s3.<region>.amazonaws.comS3-compatible endpoint. Must start with https://.
namestringnolast segment of atLog and status name. Lowercase letters, digits, hyphens; up to 32 characters.
prefixstringnowhole bucketMount one key prefix instead of the entire bucket.
read_onlybooleannofalseMount without write access.
mounts:
  - name: artifacts
    at: /mnt/artifacts
    bucket: acme-agent-artifacts
    prefix: nightly/
    region: us-east-1
    credentials:
      access_key_id: ${secrets.S3_ACCESS_KEY_ID}
      secret_access_key: ${secrets.S3_SECRET_ACCESS_KEY}

callbacks

Webhook event filtering. Optional. If omitted, the platform uses the WEBHOOK_URL and WEBHOOK_TOKEN environment variables (injected per-session by the platform).

The manifest callbacks section takes precedence over env vars when set.

FieldTypeRequiredDefaultDescription
urlstringno--Webhook endpoint URL. Supports ${secrets.KEY} substitution.
authstringno--Authorization header value (e.g. bearer ${secrets.WEBHOOK_TOKEN}). Supports ${secrets.KEY} substitution.
eventsarray of stringsnoallEvent types to send. If omitted, all events are sent.
callbacks:
  url: ${WEBHOOK_URL}
  auth: bearer ${WEBHOOK_TOKEN}
  events:
    - environment.ready
    - environment.failed
    - command.completed
    - command.failed

secrets

Secrets are referenced using ${secrets.KEY} syntax. References are valid in any string field anywhere in the manifest, and are substituted with their plaintext values by the platform before the manifest is delivered to your VM. See Secret references below for the full contract.

env:
  NPM_TOKEN: ${secrets.NPM_TOKEN}
  DATABASE_URL: postgres://user:${secrets.DB_PASSWORD}@localhost:5432/app
  STRIPE_KEY: ${secrets.STRIPE_SECRET_KEY}

services:
  - name: web
    run: node server.js --auth=${secrets.WEB_AUTH}
    env:
      ANALYTICS_KEY: ${secrets.ANALYTICS_KEY}
    healthcheck:
      url: http://localhost:3000/health?token=${secrets.HEALTH_TOKEN}

You do not declare secrets in the manifest itself; the platform's secret store is the source of truth. Manage secrets through the platform's secrets API (one per environment).

docker_compose

Alternative to setup/services/commands for Docker Compose-based projects. Mutually exclusive with the standard mode fields.

FieldTypeRequiredDefaultDescription
app_servicestringyes--Primary service name in docker-compose.yml.
setuparray of stringsno[]Commands to run before docker-compose up.
execarray of stringsno[]Commands to run inside app_service after services are up.
docker_compose:
  app_service: app
  setup:
    - cp .env.example .env
  exec:
    - php artisan migrate
    - php artisan db:seed

Health Check Format

Health checks verify that a setup command succeeded or a service is ready. One of command or url is required.

FieldTypeRequiredDefaultDescription
commandstringno*--Shell command that must exit 0.
urlstringno*--URL to probe. Supports http://, https://, tcp:// schemes. HTTP expects 2xx. TCP expects connection accepted.
intervaldurationno2sTime between probes.
timeoutdurationno60sTotal time before giving up.
retriesintegerno30Max probe attempts.
start_perioddurationno0sGrace period before the first probe.

Duration format: a decimal number followed by a unit. Supported units include ns, us, ms, s, m, and h. Examples: 500ms, 2s, 5m, 1.5h.

# HTTP health check
healthcheck:
  url: http://localhost:3000/health
  interval: 2s
  timeout: 30s
  retries: 15

# TCP health check (database)
healthcheck:
  url: tcp://localhost:5432

# Command health check
healthcheck:
  command: test -f /tmp/ready
  retries: 10

Port Definitions

Port declarations on services tell the platform which ports to expose publicly.

FieldTypeRequiredDefaultDescription
portintegeryes--Port number inside the VM.
namestringyes--Unique name for this port mapping. The platform uses this name to look up the external URL.
protocolstringnotcpProtocol hint: http, ws, or tcp.
ports:
  - port: 3000
    name: web
    protocol: http

When the VM boots, the platform allocates a host port and creates a proxy. The resulting public URL is available in the VM's port_mappings array, keyed by name.

Service Dependencies

The depends_on field on services creates a startup DAG (directed acyclic graph). Services start in dependency order via topological sort. Each service blocks until all of its dependencies' health checks pass.

Circular dependencies are detected and rejected at startup.

services:
  - name: database
    run: docker compose up postgres
    healthcheck:
      url: tcp://localhost:5432

  - name: redis
    run: docker compose up redis
    healthcheck:
      url: tcp://localhost:6379

  - name: api
    run: npm run start
    depends_on: [database, redis]
    healthcheck:
      url: http://localhost:4000/health

In this example, database and redis start concurrently. api waits for both to be healthy before starting.

Secret references

Anywhere a string appears in the manifest, ${secrets.KEY} is a valid reference. The platform substitutes every reference with its plaintext value before the manifest is delivered to your VM; your VM never receives a literal ${secrets.X} placeholder and never receives a copy of any secret it doesn't reference.

Where references are valid

Every string field in the manifest, including:

  • env values at the top level and inside any setup command, service, or named command
  • setup[].run, services[].run, commands.<name>.run, simulators[].build, simulators[].run
  • setup[].working_dir, services[].working_dir, commands.<name>.working_dir
  • setup[].healthcheck.command, setup[].healthcheck.url, and the same fields on services
  • callbacks.url, callbacks.auth
  • sources[].repo, sources[].branch, sources[].workspace, sources[].identity.name, sources[].identity.email
  • agent.command, agent.args[], agent.instructions[], agent.env values
  • docker_compose.app_service, docker_compose.setup[], docker_compose.exec[]

Source of values

${secrets.KEY} references resolve from the secret store on the environment. You manage secrets through the platform's secrets API; you do not declare them in the manifest. See the secrets section for usage examples.

Missing references are rejected at the API

If the manifest references a secret that is not configured on the environment, the platform rejects the request before a VM is provisioned. This happens in two places:

  1. At manifest acceptance, when you save a new manifest or update an existing one. The save fails with a 422 and an error listing every missing secret and every field that referenced one.
  2. At VM provisioning, when a session boot or VM-create call resolves the manifest to a delivery-ready form. If a referenced secret was deleted in the window between manifest save and VM-create, the VM-create fails with a 422 and the same diagnostic.

Either way, your VM never starts against a literal ${secrets.X} placeholder. A typo (${secrets.STRIP_KEY} instead of ${secrets.STRIPE_KEY}) is caught the moment you save the manifest. Forgetting to provision the secret is caught at the same point. The fix in both cases is to add the secret to the environment, or to remove the reference, then re-save.

Quoting is your responsibility

Substitution is a literal string replacement. The plaintext value lands in the manifest field exactly as stored, with no escaping applied for the surrounding context. If your secret contains shell metacharacters ($, ", ', backticks, spaces, newlines) and you reference it inside a run string, quote it the same way you would quote any other unknown input:

setup:
  - name: bad-quoting
    run: echo "Token is ${secrets.API_TOKEN}" # breaks if the value contains a "

  - name: good-quoting
    run: |
      printf '%s' "$TOKEN" > .auth   # value flows through env, no shell parsing
    env:
      TOKEN: ${secrets.API_TOKEN}

Treating secret values as data passed via env and consumed by printf '%s', cat, or argument lists is the safe pattern. Inlining secret values into a shell-parsed command line is brittle for the same reason inlining any other untrusted string is.

Rotating a secret requires a new snapshot

The platform substitutes the manifest with the secret's current plaintext at the moment a VM is created. The captured snapshot is a frozen filesystem that may include side effects from setup commands, such as files written or packages configured with the value baked in. Rotating a secret with PATCH /v1/.../secrets/{id} updates the source of truth, but it does not retroactively change files or build artifacts already captured in existing snapshots.

If a secret is consumed by setup, regenerate the environment's base snapshot after rotation. New sessions booted from the new snapshot use the rotated value through the full lifecycle.

What Gets Auto-Injected

Wallfacer automatically modifies the manifest before execution:

BehaviorDetail
agent-api serviceIf no service named agent-api exists, one is injected as the first service with a health check, port declaration, and platform-appropriate binary path.
Memory and vCPUsNormalized from server configuration. User-provided values are overwritten.
Simulator device and runtimeMissing iOS device and runtime values default to the configured device and latest. Selected values are preserved. The agent resolves latest to the newest installed runtime compatible with the device at boot.
Map field normalizationenv and commands are ensured to be objects (not arrays) for correct parsing.
Port normalizationBare integer ports in service definitions are converted to proper port objects.

Configuration Layering

Manifests are resolved by merging three layers, where later layers override earlier ones:

1. Repo file (agent-env.yml in repo root)     -- committed by engineers
2. Environment config (stored in Wallfacer)    -- per-environment overrides via app/API
3. Session config (injected at session start)  -- per-session secrets and branch overrides

Merge semantics: Deep merge with later layers winning. For arrays (setup, services), the override layer replaces the entire array. For objects (env, agent, commands), keys are merged with override values winning.

Examples

Node.js Web App with PostgreSQL

version: 1

platform:
  os: linux/amd64

sources:
  - repo: github.com/acme/webapp
    branch: main

env:
  NODE_ENV: development
  DATABASE_URL: postgres://postgres:postgres@localhost:5432/app

setup:
  - name: Install dependencies
    run: npm install
    retries: 2
    healthcheck:
      command: test -d node_modules

services:
  - name: database
    run: docker compose up postgres
    healthcheck:
      url: tcp://localhost:5432

  - name: dev-server
    run: npm run dev
    depends_on: [database]
    ports:
      - port: 3000
        name: web
        protocol: http
    healthcheck:
      url: http://localhost:3000/health

commands:
  migrate:
    run: npm run db:migrate
    working_dir: /workspace/webapp
  seed:
    run: npm run db:seed
    working_dir: /workspace/webapp

ready: [migrate, seed]

Python Django Project

version: 1

platform:
  os: linux/amd64

sources:
  - repo: github.com/acme/django-api
    branch: main

env:
  DJANGO_SETTINGS_MODULE: config.settings.development
  DATABASE_URL: postgres://postgres:postgres@localhost:5432/django

setup:
  - name: Install Python dependencies
    run: pip install -r requirements.txt
    retries: 2
  - name: Install pre-commit hooks
    run: pre-commit install

services:
  - name: database
    run: docker compose up -d postgres
    healthcheck:
      url: tcp://localhost:5432

  - name: django
    run: python manage.py runserver 0.0.0.0:8000
    depends_on: [database]
    ports:
      - port: 8000
        name: web
        protocol: http
    healthcheck:
      url: http://localhost:8000/health/

commands:
  migrate:
    run: python manage.py migrate
    working_dir: /workspace/django-api
  seed:
    run: python manage.py loaddata fixtures/seed.json
    working_dir: /workspace/django-api

ready: [migrate]

agent:
  instructions:
    - .claude/CLAUDE.md

iOS App with Simulator

version: 1

platform:
  os: darwin/arm64

sources:
  - repo: github.com/acme/ios-app
    branch: main

setup:
  - name: Install CocoaPods dependencies
    run: pod install
    retries: 2
    healthcheck:
      command: test -d Pods

simulators:
  - platform: ios
    bundle_id: com.acme.ios-app
    build: |
      xcodebuild -workspace App.xcworkspace -scheme App \
        -destination "id=$IOS_SIMULATOR_UDID" \
        -derivedDataPath .build build
      xcrun simctl install $IOS_SIMULATOR_UDID \
        .build/Build/Products/Debug-iphonesimulator/App.app
      xcrun simctl launch $IOS_SIMULATOR_UDID com.acme.ios-app

agent:
  instructions:
    - .claude/CLAUDE.md

Multi-Repo Setup

version: 1

platform:
  os: linux/amd64

sources:
  - repo: github.com/acme/frontend
    workspace: /workspace/frontend
    branch: main
  - repo: github.com/acme/shared-lib
    workspace: /workspace/shared-lib
    branch: main

env:
  NODE_ENV: development

setup:
  - name: Install shared library
    run: cd /workspace/shared-lib && npm install && npm run build
    retries: 2
  - name: Install frontend dependencies
    run: cd /workspace/frontend && npm install
    retries: 2
  - name: Link shared library
    run: cd /workspace/frontend && npm link ../shared-lib

services:
  - name: dev-server
    run: npm run dev
    working_dir: /workspace/frontend
    ports:
      - port: 3000
        name: web
        protocol: http
    healthcheck:
      url: http://localhost:3000

agent:
  instructions:
    - .claude/CLAUDE.md