A native SwiftUI client for Agent Fleet. It covers runs grouped by machine, a live chat for each run (streamed over SSE, with subagents nested under their Agent calls), a composer, and a new-run sheet. It talks to the same HTTP API as the web app and has no third-party dependencies.
The source lives in ios/ of Agent Fleet's own repo. ios/scripts/publish-github.sh copies it to the public Porter-smith/fleet-ios, where GitHub's macOS runners build it. Make changes in agent-fleet, not in the GitHub copy.
FleetKit/: a Swift package with the API models, the client, the SSE line splitter and parser, transcript building, composer controls (ported from the web app's) and the demo mode. The unit tests decode fixtures captured from the Fleet dev server.Fleet/: the SwiftUI app (iOS 17+, Observation, async/await).FleetUITests/: a UI test that walks the app in demo mode and saves screenshots.project.yml: the XcodeGen spec. The.xcodeprojis generated and not committed.
You need a Mac with Xcode 16 or newer, plus brew install xcodegen.
From this folder (ios/ in agent-fleet, the root of fleet-ios):
xcodegen generate
open Fleet.xcodeproj # run the Fleet scheme on a simulator
(cd FleetKit && swift test) # unit tests, no simulator neededLaunch with the argument -FleetDemo, or tap Try the demo on the pairing screen, and the app runs on the fixtures bundled in FleetKit/Sources/FleetKit/Demo/ through a stand-in URLProtocol. No server or token is needed, and nothing is saved. The fixtures are real responses from the Fleet dev server, with their times moved to now.
To use a real server instead, run agent-fleet's dev server from that repo's root with bun scripts/dev-fleet.ts. It serves fixture runs on http://127.0.0.1:8793 with the admin token dev, and needs tmux. Pair the simulator with that URL and token. Or set FLEET_URL and FLEET_TOKEN in the scheme's environment and the app pairs itself.
The same way CI runs them, in demo mode:
TEST_RUNNER_SCREENSHOT_DIR=$PWD/screenshots \
xcodebuild test -project Fleet.xcodeproj -scheme Fleet \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' CODE_SIGNING_ALLOWED=NOAdd TEST_RUNNER_FLEET_URL=http://127.0.0.1:8793 TEST_RUNNER_FLEET_TOKEN=dev to walk the dev server instead.
CI (.github/workflows/ios.yml) runs on GitHub's macOS runners in fleet-ios, on each push to main and on pull requests. It needs no secrets and no server. It uploads the screenshots as the screenshots artifact.
A second workflow, .github/workflows/adhoc.yml, builds a signed ipa for your iPhone. See Install on an iPhone.
From agent-fleet, after committing changes to ios/:
ios/scripts/publish-github.sh --dry-run # export and scan only
ios/scripts/publish-github.sh # then commit and push to fleet-ios mainThe script exports the committed ios/ tree (without scripts/), scans it for tokens, keys, email addresses, private IPs and private host names, and stops if anything turns up. It then commits on top of fleet-ios's own history as the GitHub noreply address, so agent-fleet's own history stays private.
- Make a Fleet token with the scopes
runs:read,runs:controlandruns:spawn. Create it in the web app under Settings > API tokens (POST /api/tokens, admin only). The secret is shown once. An admin token also works, but a scoped token is safer on a phone.runs:readcovers the runs list, transcripts, the live stream and subagents.runs:controlcovers send, queue, interrupt, stop, restart and permission answers.runs:spawncovers new runs.
- In the app, enter the server URL. Use the Tailscale name or a LAN address, for example
https://fleet.example.ts.net. Then paste the token. - The app checks the token against
GET /api/mebefore saving. The token goes in the Keychain (this device only, readable after first unlock) and the URL in the app's settings. Settings lists any missing scopes and has Unpair.
The server sits behind Caddy with its internal CA, so iOS rejects the certificate until you trust that root:
- Get Caddy's root certificate,
root.crt, from Caddy's data dir underpki/authorities/local/. AirDrop or email it to the phone, or serve it once. - Open it, then go to Settings > General > VPN & Device Management and install the profile.
- Go to Settings > General > About > Certificate Trust Settings and turn on full trust for that root.
If the certificate isn't trusted, the app says so when you pair. Plain http:// works only for local addresses (127.0.0.1, .local and LAN or Tailscale IPs), which App Transport Security allows through NSAllowsLocalNetworking.
- Runs list: there is no list-wide stream, so the list polls
GET /api/runsevery 4 s while it's on screen. Pull to refresh. Active/All filter. - Chat:
GET /api/runs/:id/streamreplays recent events, then streams live events and text deltas. On a dropped connection the app reconnects with backoff and resumes after the last seq it has. Load earlier pages back through the transcript. Each Agent/Task call expands to show its subagent's own tool calls and messages. Agents lists the session's background subagents. - Composer: each button follows the harness's capabilities from
GET /api/runs/:id. Anything the harness can't do, or the run's state forbids, is disabled, with the reason shown in the actions menu.- Queue waits for the end of the turn.
- Send now goes into the live turn, only where the harness supports mid-turn input.
- Interrupt ends the current turn but keeps the session.
- Stop ends the process.
- Restart resumes the session as a new run.
Open https://porter-smith.github.io/fleet-ios/ in Safari on the iPhone and tap Install Fleet. This works like Expo's internal distribution: an ad hoc signed build, installed from an itms-services:// link. It installs only on iPhones registered on the Apple developer team (team GDTA3U9KK4).
- The first time, iOS asks for Developer Mode: Settings > Privacy & Security > Developer Mode, then restart. iOS shows that switch only after you've tried to install an ad hoc app once.
- To update, open the page and tap Install again. Each build has a higher
CFBundleVersion(the workflow's run number), so the new one replaces the old and keeps the app's data and the Keychain token.
.github/workflows/adhoc.yml makes the build on each push to main of fleet-ios, and on demand from Actions > Ad hoc install > Run workflow. It never runs on pull requests. Without the signing secrets it skips the build and still passes. Each run:
ci/asc.pyregisters the bundle IDdev.portersmith.fleetand any devices inIOS_DEVICE_UDIDSthrough the App Store Connect API. It then reuses the ad hoc profile Fleet Ad Hoc CI, or remakes it when a device was added, the certificate changed, or it expires within 14 days. The profile covers every enabled iOS device on the team.- It imports the Apple Distribution certificate into a throwaway keychain, archives with manual signing, and exports with
method: release-testing(ad hoc). - It checks the signature, then publishes
Fleet-<version>-<build>.ipa,manifest-<build>.plist(also copied tomanifest.plist) andindex.htmlto GitHub Pages. The install link carries the manifest URL percent-encoded, with no query strings anywhere: iOS answers a raw or query-string manifest URL with "Open in iTunes?" and then does nothing. Pages has a valid HTTPS certificate, whichitms-servicesrequires, so you don't need to trust Caddy's CA for this.
The repo, the Pages site and its artifacts are public. Nothing secret goes in them. The signing keys stay in Actions secrets, they're used only on the macOS runner, and the runner deletes the keychain afterwards. Logs show counts, never UDIDs. Like any ad hoc ipa, though, the one on the page carries its provisioning profile. Anyone who downloads it can read the team ID, the name on the certificate (Porter Smith) and the UDIDs of the registered devices. Without a device being registered on your team, nobody can install the app, and a UDID alone gives no access to the phone.
You don't need a Mac for any of this. It was done on 2026-10-14. Repeat it when the certificate expires (2027-10-14) or if you move to a new team.
-
API key. Go to App Store Connect, then Users and Access > Integrations > App Store Connect API > Team Keys, and generate a key with the Admin role. Admin can create the distribution certificate. Each build afterwards only manages bundle IDs, devices and profiles, so you can swap in an App Manager key for
ASC_KEY_*. If builds then fail with a 403, go back to Admin. Note the Key ID and the Issuer ID (above the keys table), and download the.p8file. Apple lets you download it only once. -
Team ID. Find it at developer.apple.com, under Account > Membership details. For this team it's
GDTA3U9KK4. -
iPhone UDID. It's only needed if the phone isn't registered yet. Check under Certificates, Identifiers & Profiles > Devices on developer.apple.com: an iPhone added for Expo builds is already there. Without a Mac, you can get it in any of these ways:
- On Windows, plug the phone in, open iTunes or Apple Devices, and click the serial number until it shows the UDID.
- On Linux, plug the phone in and run
idevice_id -l(libimobiledevice). - On the phone, open a UDID site such as udid.tech in Safari and install its profile. That site sees the UDID.
Put it in the
IOS_DEVICE_UDIDSsecret (comma-separate several). Or add it on developer.apple.com. The next build includes it. -
Certificate and secrets. Run this on any Linux or macOS box with
openssl,python3and an authenticatedgh:export ASC_KEY_ID=... ASC_ISSUER_ID=... ASC_KEY_PATH=~/private/AuthKey_XXXX.p8 APPLE_TEAM_ID=GDTA3U9KK4 ci/setup-signing.sh ~/private/fleet-ios # a 0700 folder outside any repo
It makes an RSA key and CSR, creates an Apple Distribution certificate from them through the API, and packs both into
dist.p12in that folder. Then it sets these Actions secrets on fleet-ios:Secret What APPLE_TEAM_IDTeam ID ASC_KEY_ID,ASC_ISSUER_IDAPI key ID and issuer ID ASC_KEY_P8Contents of the .p8DIST_CERT_P12,DIST_CERT_PASSWORDThe distribution certificate and key (base64 p12), and its password IOS_DEVICE_UDIDSOptional: devices to register Keep that folder. It holds the certificate's only private key. If you lose it, run the script again with an empty folder. That makes a new certificate (a team can hold 3 distribution certificates). Revoking a certificate stops every app signed with it from opening.
-
Pages. Under the repo's Settings > Pages, set the source to GitHub Actions (already done).
- TestFlight or the App Store: an App Store export plus an upload step, using the same API key.
- Push notifications (APNs) for "needs you" alerts: the push entitlement and a server-side sender. Live Activities come after that.