This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Set up a project for agentic workflows

Edit page

Set up an Expo project one time for the agentic workflow guides, with a build profile, tokens, an agent runner, and a device controller.


Each agentic workflow guide uses the same project setup. Do the steps on this page one time for your project. Then add the workflows from the guides.

Agentic CI and QA Swarm need the steps from Add a build profile for the remote device to Push the files. Issue to PR and Bug triage also need Add a GitHub token to EAS.

Prerequisites

5 requirements

1.

An Expo project in a GitHub repository

The project must use Continuous Native Generation (CNG), so the repository does not include the android and ios directories. The app config must set the app ID for the platform that you test. A new project does not set it. Add it to app.json:

app.json
{ "expo": { "android": { "package": "com.yourcompany.yourapp" } } }

2.

EAS CLI

Install EAS CLI:

Terminal
- npm install --global eas-cli
- yarn global add eas-cli
- pnpm add --global eas-cli
- bun add --global eas-cli

3.

GitHub CLI

Issue to PR and Bug triage use GitHub CLI to add a repository secret, a label, and an issue. Install it, then sign in:

Terminal
- gh auth login

4.

Expo Simulators access

The account that owns the project needs Expo Simulators access. In your project directory, link the project to EAS and check the access:

Terminal
- eas login
- eas init
- eas simulator:availability --json

An account with access returns "available": true. If the value is false, join the waitlist to request access.

5.

A coding agent that can run in CI

The workflows run the agent from the command line, without a person. You can use any coding agent that has this mode and a key that works without a browser login. Add the agent runner has a ready-made runner for Claude Code, Codex, Cursor, and Grok. Create the key for your agent:

AgentHow to get the key
Claude CodeRun claude setup-token. The token requires a Pro, Max, Team, or Enterprise plan. An Anthropic API key also works.
CodexCreate an OpenAI API key. The Codex docs recommend an API key for CI jobs.
CursorIn the Cursor dashboard, open API Keys.
GrokCreate an xAI API key on console.x.ai.

1

Add a build profile for the remote device

The workflows install a build from the preview profile on each session. Merge these keys into the preview profile in eas.json, or add the profile if it does not exist:

An Android Emulator session runs an APK:

eas.json
{ "build": { "preview": { "distribution": "internal", "environment": "preview", "android": { "buildType": "apk" } } } }

EAS signs the APK with an Android keystore. If the project has no Android keystore yet, create one before the first run. Run this command, select the preview profile, and let EAS generate a new keystore:

Terminal
- eas credentials --platform android

To test both platforms, add the android and the ios keys to the same profile. The fingerprint job in each workflow uses the same preview environment, so the fingerprints match the builds.

If your preview profile already makes builds for physical devices, ios.simulator: true stops those builds. In that case, add a separate profile for the agent, such as agent. Then change profile: preview to the name of that profile in each workflow file.

2

Connect GitHub to the EAS project

The workflows start on GitHub events and post comments on PRs. Connect your GitHub repository to the EAS project. See Automate workflows with GitHub events.

If the app is in a subdirectory of the repository, such as apps/my-app, set the Base directory to that path. See Configure your repository settings and Use a monorepo.

3

Create a robot token

The jobs start sessions with the access token of a robot user. A robot user cannot sign in to Expo, and it can do only what its role allows.

In the account that owns the project, open Access tokens. Under Robot users, create a robot user with the Developer role. The Developer role is the lowest role that can start and stop sessions. Then create a token for the robot user. See What the agent can do for more information.

Keep this token and the key of your agent out of files, logs, and chat.

4

Add the tokens to EAS

In the project dashboard, open Project settings > Environment variables. Select Add Variables. Add these variables with the Secret visibility:

NameValue
EXPO_TOKEN_SIMULATORThe robot token
CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, CODEX_API_KEY, CURSOR_API_KEY, or XAI_API_KEYThe key of your agent

Add the variables to the preview environment. The workflows use preview for the builds and for the agent, and the agent can read each variable of that environment. So keep no other secrets in preview. Values that are not secret, such as EXPO_PUBLIC_ variables, can stay, because they are in the app already. If preview must have another secret, such as an NPM_TOKEN for private packages, the agent can read that secret too.

If preview has a SENTRY_AUTH_TOKEN, remove it from preview, and add SENTRY_DISABLE_AUTO_UPLOAD with the value true. Then the builds do not upload source maps or debug files to Sentry.

On the Enterprise and Production plans, you can create a custom environment for the agent. Then change environment: preview to the name of that environment in the build profile and in each workflow file.

Check that the list has the variables:

Terminal
- eas env:list --environment preview

5

Add the agent runner

This file runs your agent without a person. Use the version for your agent. The jobs run it as bash .eas/agents/run-agent.sh <prompt-file> <mode>. The mode is device when the agent tests the app, and change when the agent changes the code. For another agent, write a runner that takes the same two arguments and runs the agent without a person.

.eas/agents/run-agent.sh
#!/usr/bin/env bash set -euo pipefail case "$2" in device) tools='Read,Glob,Grep,Write,Bash(npx --yes eas-cli@latest simulator:exec npx --yes agent-device@latest *),Bash(npx --yes -p @swmansion/argent@latest argent *)' ;; change) tools='Read,Glob,Grep,Edit,Write,Bash(npx tsc --noEmit)' ;; *) echo "Unknown mode \"$2\". Use device or change." >&2 exit 1 ;; esac mkdir -p agent-output/evidence npx --yes @anthropic-ai/claude-code@latest -p "Do the task that standard input describes." \ --allowedTools "$tools" --permission-mode dontAsk --max-turns 80 \ --output-format stream-json --verbose < "$1" > agent-output/evidence/agent-log.jsonl || { tail -n 1 agent-output/evidence/agent-log.jsonl >&2 exit 1 }

The change mode allows only npx tsc --noEmit, so the agent cannot run the tests or the linter of your project. To allow them, add their commands to the change mode. For example, add Bash(bun run test) to the Claude Code runner, or --allow 'Bash(bun run test)' to the Grok runner.

6

Add the device controller

The agent controls the remote device with a controller: agent-device or Argent. This script starts an Expo Simulators session for the controller. Then it writes the device details and the commands of the controller to agent-output/device.md, and the job adds this file to the prompt of the agent. Use the version for your controller.

The jobs run the script as source .eas/agents/start-session.sh <platform> <build ID> <session name> <tag>. The source command runs the script in the shell of the job, so the job keeps the session details and stops the session when it ends. The script reads the app ID with npx expo config, so the app ID can come from app.json, app.config.js, or app.config.ts.

.eas/agents/start-session.sh
#!/usr/bin/env bash case "$1" in android) APP_ID_KEY=package ;; ios) APP_ID_KEY=bundleIdentifier ;; *) echo "Unknown platform \"$1\". Use android or ios." >&2 exit 1 ;; esac APP_ID=$(npx expo config --json | node -e 'const config = JSON.parse(require("fs").readFileSync(0, "utf8")); console.log(config[process.argv[1]]?.[process.argv[2]] ?? "")' "$1" "$APP_ID_KEY") : "${APP_ID:?The app config has no app ID for $1. Add it to app.json or app.config.js.}" mkdir -p agent-output/evidence export EXPO_TOKEN="$EXPO_TOKEN_SIMULATOR" npx --yes eas-cli@latest simulator:start --platform "$1" --type agent-device \ --build-id "$2" --name "$3" --tag "$4" \ --max-duration-minutes 30 --json > /tmp/session.json trap 'npx --yes eas-cli@latest simulator:stop --non-interactive > /dev/null 2>&1 || true' EXIT npx --yes eas-cli@latest simulator:exec npx --yes agent-device@latest open "$APP_ID" --platform "$1" > /dev/null cat > agent-output/device.md << EOF ## Device - Platform: $1 - App ID: $APP_ID Control the device only with this shell command: npx --yes eas-cli@latest simulator:exec npx --yes agent-device@latest <command> Run each command alone. Do not join commands with ;, &&, or |. The folder agent-output/evidence already exists. If a command is denied, run it again alone one time before you stop. Write files, such as the verdict, with your file tools, not with shell commands. Use these agent-device commands: - snapshot -i lists the elements on the screen, with references such as @e2. The references change on each screen, so run snapshot -i again after each action. - snapshot without -i also lists the plain text on the screen. - press @e2 taps an element. - fill @e4 "text" types text into a field. - keyboard enter presses the Enter key of the keyboard. - scroll down scrolls the screen. - screenshot agent-output/evidence/<number>-<name>.png saves a screenshot. - open $APP_ID --platform $1 opens the app again. EOF if [ -f .eas/agents/app-facts.md ]; then printf '\n## App facts\n\n' >> agent-output/device.md cat .eas/agents/app-facts.md >> agent-output/device.md fi

Each session starts on a new install of the app. If the agent needs facts about your app, add them to .eas/agents/app-facts.md. The script adds this file to agent-output/device.md, so each workflow gives the facts to the agent. For example, write how to pass the onboarding screens, the text of each main screen on each platform, and what the agent must not touch:

.eas/agents/app-facts.md
- The app opens on two onboarding screens. Select Next on each screen to reach the main screen. - The title of the main screen is "Tasks" on iOS and "My App" on Android. - Do not sign out, and do not delete the account.

7

Push the files

The sessions and the jobs write files to your project directory. The .env.eas-simulator file has a token for the session. Add these lines to .gitignore:

.gitignore
.env.eas-simulator agent-output/

Then push the files:

Terminal
- git add .gitignore eas.json .eas/agents/run-agent.sh .eas/agents/start-session.sh
- git commit -m "Add the agent runner and the device controller"
- git push

If you changed app.json, also add it. If you use Cursor, also add .cursor/cli.json. If you added app facts, also add .eas/agents/app-facts.md.

8

Add a GitHub token to EAS

Issue to PR and Bug triage need this step. Issue to PR pushes a branch, opens a PR, and adds comments to the issue. Bug triage creates issues for TestFlight feedback and posts its verdict on the issue. QA Swarm needs this step only to post its report on a PR. These tasks need a GitHub token.

On GitHub, create a fine-grained personal access token. Under Repository access, select only your repository. Give it these permissions:

  • Contents: Read and write
  • Issues: Read and write
  • Pull requests: Read and write

In the project dashboard, open Project settings > Environment variables. Add these variables to the preview environment:

NameValueVisibility
GITHUB_TOKENThe GitHub tokenSecret
GITHUB_REPOSITORYThe owner and the name of the repository, such as acme/appPlain text

The job removes the GitHub token from the environment of the agent. An agent that can run shell commands can still read the variables of the job. So give the token access only to this repository. See What the agent can do for more information.

GitHub shows the owner of the token as the author of each PR that Issue to PR opens. GitHub does not let an author approve their own PR. If your repository requires a review, another person must approve these PRs. To keep your own approval, create the token with a separate GitHub account for the agent.

Test the setup by hand

Before you add a workflow, you can test the build and the controller from your computer. This test uses agent-device on iOS.

Create a build with the profile from Add a build profile for the remote device:

Terminal
- eas build --platform ios --profile preview

Then run these commands in your project directory. The simulator:start command fails outside a project directory. Replace <build ID> with the ID of the build, and <app ID> with the ios.bundleIdentifier of your app:

Terminal
- eas simulator:start --platform ios --build-id <build ID>
- eas simulator:exec npx --yes agent-device@latest open <app ID> --platform ios
- eas simulator:exec npx --yes agent-device@latest snapshot -i
- eas simulator:stop

The simulator:start command writes the session details to .env.eas-simulator. The simulator:exec command reads this file, so it does not need an Expo login. The snapshot -i command lists the elements on the screen, such as buttons and text. If the list shows your app, the build and the controller work.

Use a monorepo

The guides show each file at the root of the app. In a monorepo, the app is in a subdirectory of the repository, such as apps/my-app. The jobs run in that directory, so most files work without a change. Make these changes:

  • Put .eas and the other files of the guides in the app directory, such as apps/my-app/.eas/agents/run-agent.sh. Put the GitHub Action of Issue to PR in .github/workflows at the root of the repository.

  • The eas/upload_artifact step reads path from the root of the repository. In each of these steps, put the app directory in front of the path. For example, use apps/my-app/agent-output/evidence/**/*. In Issue to PR, use /tmp/agent-change/apps/my-app/agent-output/**/*.

  • The GitHub Action of Issue to PR runs its commands at the root of the repository. Run them in the app directory:

    .github/workflows/agent-label.yml
    jobs: start: defaults: run: working-directory: apps/my-app

The job script of Issue to PR finds the root of the repository and the app directory with Git, so it needs no change.

What the agent can do

The agent reads text from PRs, issues, and TestFlight feedback. Another person can write instructions for the agent in that text. These workflows limit what the agent can do:

  • The agent can read each variable of the environment that the job uses, and the session file .env.eas-simulator. This file has a token for the current session. Keep no other secrets in that environment. See Add the tokens to EAS.
  • EAS does not start pull_request workflows for PRs from forks.

Each agent also has its own limits:

AgentLimit in these workflows
Claude Code--allowedTools allows the file tools and the commands of one mode. The device mode allows agent-device through eas simulator:exec, and the Argent CLI. The change mode allows npx tsc --noEmit.
CodexThe runner does not read the mode, so the device and the change jobs give the agent the same rights. The agent can run any command on the EAS worker. Each job runs on its own worker.
CursorThe runner does not read the mode, so the device and the change jobs give the agent the same rights. The agent can run each command that .cursor/cli.json does not deny. The file allows Shell(npx), and npx can run any npm package. So the deny list is a weak limit.
Grok--allow allows file changes and the commands of one mode. The device mode allows agent-device through eas simulator:exec, and the Argent CLI. The change mode allows npx tsc --noEmit. Grok also runs its read-only commands, such as ls and cat. It does not run other commands.
Other agentThe limits depend on the permission settings of its command-line tool.

The code of a branch also runs in some workflows. In Agentic CI, the check job checks out the PR. In QA Swarm, each lane checks out the branch of --ref. So the code of that branch can change .eas/agents/run-agent.sh and the workflow files, and read EXPO_TOKEN_SIMULATOR and the key of your agent. It can also read GITHUB_TOKEN if you did Add a GitHub token to EAS. Each person who can push a branch to the repository can add such code.

So use a robot token with the Developer role, not your personal access token. A PR that reads the robot token can do only what the Developer role allows.