Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 30 additions & 56 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
`agent-device` is a CLI and daemon for automating Apple, Android, HarmonyOS, Vega, Linux, and web
targets. A long-lived daemon owns sessions; registry-derived commands route to platform runtimes.

This file contains repository-specific traps and invariants that are expensive to rediscover. Load
task procedures only when needed:
## Task routing

Load only the procedures relevant to the task:

| When the task involves | Read |
| --- | --- |
Expand All @@ -14,35 +15,28 @@ task procedures only when needed:
| Selector capture, polling, or interaction fast paths | `docs/agents/selector-capture.md` |
| Adding or changing a CLI flag | `docs/agents/cli-flags.md` |
| Opening or reviewing a PR | `docs/agents/pull-requests.md` |
| Running against a real device | `docs/agents/device-verification.md` |
| Apple runner changes or manual device verification | `docs/agents/device-verification.md` |
| Writing issues or PRDs, and triage labels | `docs/agents/issue-tracker.md`, `docs/agents/triage-labels.md` |
| Web backend setup or diagnostics | `docs/agents/web-backend.md` |

Versioned CLI help is the source of truth for command behavior. Start with `agent-device help
workflow`, then the relevant topic help.
Versioned CLI help owns command semantics. Read the relevant topic when behavior is unclear;
normal app-driving startup follows the skill's routing card.

## Incident-derived principles

- Guarantees erode at path boundaries. Classify every interaction dispatch path in
`packages/contracts/src/interaction-guarantees.ts`; a registry claim is not proof that the native
implementation satisfies the guarantee's definition.
- Classify interaction dispatch paths in `packages/contracts/src/interaction-guarantees.ts` and
verify each claim against the native implementation.
- Delegation on error proves no success-path parity. A fast path may succeed on a candidate the
shared rules would refuse.
- Prove that a code path can fire before measuring it. An A/B test with an unreachable arm is two
green runs without evidence.
- A green regression test counts only after it has been observed red against the pre-fix code. Plant
a violation for new structural gates and verify the gate names the invariant.
- Repair recurring failures at their owning interface with types, a registry, or one construction
path. A custom guard that reconstructs another source of truth needs redesign, not another
exception.
- Treat explanatory implementation comments as a failed design review. Do not narrate control flow,
preserve review history, or justify a workaround in code. Express the invariant through names,
types, module boundaries, and tests; put history in the PR or an ADR. Allow only public API docs,
tool directives, and a brief citation to an external constraint that cannot be encoded.
- Key behavior on typed reasons and details, never error text. Existing message sniffs are owned debt
and must not be copied.
- Snapshot output is the token budget: do not add per-node metadata when response-level metadata can
be emitted once. Append warnings through the shared response builder; never replace prior warnings.
- Prove both paths are reachable before measuring a fast path against its baseline.
- Repair recurring failures at the owning type, registry, or construction path; do not add guards
that reconstruct another source of truth.
- Keep control-flow narration and review history out of implementation comments. Encode invariants
in names, types, boundaries, and tests; reserve comments for public API docs, tool directives,
and non-obvious constraints that cannot be encoded.
- Key behavior on typed reasons and details, never error text; do not copy existing message sniffs.
- Keep metadata at response level when it applies to the whole snapshot. Append warnings through
the shared response builder; never replace prior warnings.
- Before preserving a compatibility shape, run `git tag --contains <commit>`. Unreleased API has no
external compatibility obligation.

Expand All @@ -66,10 +60,10 @@ and progress in `@agent-device/capture-kit` (`request-cancel`, `request-progress
contracts in `@agent-device/contracts`; CLI flags in `src/commands/cli-grammar`; cross-surface schema
composition in `src/cli-schema`.

The enforced registries are self-declaring. A failing completeness, parity, coverage, timeout,
layering, or construction gate means the new cell or path is unclassified; do not suppress or
allowlist it. Interaction responses are built only through `buildInteractionResponseData`, and
cross-language rules change through golden tables under `contracts/fixtures/`.
Resolve registry completeness failures at the missing declaration. Diagnose other gate failures
at their reported invariant; do not suppress them or add an allowlist to get a pass. Build interaction
responses through `buildInteractionResponseData`; change cross-language rules through golden tables
under `contracts/fixtures/`.

## Hard repository rules

Expand All @@ -78,9 +72,9 @@ cross-language rules change through golden tables under `contracts/fixtures/`.
- Apple target changes keep the kernel device model, runtime-fact admission, dispatch resolution,
Apple discovery, and xctestrun preparation in sync.
- iOS simulator-set scoping must never hide the host macOS desktop target.
- Skills may carry a minimal start/routing card; command semantics belong in versioned CLI help.
- Do not add compatibility or fallback behavior without explicit approval. Complete migrations and
remove superseded paths.
- Skills stay minimal routing cards.
- Add compatibility or fallback behavior only when explicitly requested or approved. Otherwise,
complete the migration and remove superseded paths.
- Keep changes within one command family or module group unless the task explicitly crosses a
boundary. Platform-neutral work does not license inspecting every platform implementation.

Expand All @@ -93,7 +87,6 @@ cross-language rules change through golden tables under `contracts/fixtures/`.
- Tests mirror source topology one-to-one. Split a source module and its test together; do not add to
the legacy `interaction.test.ts` or platform `index.test.ts` aggregations. Pure moves carry their
tests unchanged; rename-only hunks owe no new coverage.
- Shared fixtures are named exports in a sibling fixture module, not repeated inline literals.
- `src/daemon/handlers/session.ts` is already over budget; extract the relevant platform-specific
concept before adding behavior.

Expand All @@ -103,30 +96,14 @@ cross-language rules change through golden tables under `contracts/fixtures/`.
repository-wide, not path-scoped.
- A fresh worktree requires `pnpm install --frozen-lockfile && pnpm build`. Until then package and
optional-peer resolution may point at another checkout and produce false failures.
- Source-checkout daemon state is worktree-scoped, but devices are not. Use `pnpm daemon:state-dir`
to inspect it and different devices for concurrent worktrees.
- Run one full gate per host at a time. Subprocess-backed tests under concurrent worktrees produce
timeout-shaped contention failures.
- Before pushing, run `pnpm check:affected --run && git push`. Use `pnpm check` for broad refactors.
GitHub remains authoritative for native, device, provider, and full-coverage lanes.
- Parallel work needs disjoint edit ownership and distinct devices. Run one full gate per host;
concurrent subprocess-backed suites can produce contention timeouts.
- The layering scan reads tracked files only. Stage a new module before trusting its result.
- Fallow baselines are path-keyed. Move the matching baseline entry when renaming a file; never bulk
regenerate baselines to accept unrelated findings.
- The first Node process after a newly signed Apple runner launches may block during Gatekeeper
verification. Warm it with a throwaway `node -e 0` before measuring.
- `DEVICE_IN_USE` has two flavors. "already in use by session X" is this daemon — follow its
`close --session` hint. "owned by session X in workspace Y" is another worktree's device
claim — non-retriable; run the error's `device status`/`device release --stale` recovery,
never PID hunting.
- A changing timeout failure set that passes in isolation is host contention. Reproduce the same
test on `origin/main` under the same load before treating it as a regression.

## Runtime and diagnostics seams

The OS-neutral Apple runner lives under `packages/platform-apple/src/runner/`. For connection errors,
retry policy, or command typing, start at `runner-contract.ts`; transport stays below session/client
behavior, and xctestrun build/cache logic stays outside request execution.

Diagnostics use `@agent-device/capture-kit/diagnostics`. Request diagnostics belong in the session request log;
session artifact paths come from `src/daemon/session-store.ts`. App/device logs remain in `app.log`;
Apple runner and xcodebuild output remains in `runner.log`.
Expand All @@ -150,11 +127,8 @@ translate through the interaction root frame. Prefer selector or ref tests over

## Documentation ownership

Before adding guidance, decide whether the command surface, CLI grammar/help, MCP projection, daemon
runtime, ADR, or task procedure owns it. Link to executable registries instead of copying their
contents. Keep a sentence in this file only when no gate, lint rule, versioned help, ADR, or
decision-site comment can own it. `CONTEXT.md` is glossary-only: no implementation paths,
architecture decisions, migration state, or workflows.
Keep this file to repository traps and routing. Put task procedures in `docs/agents/`, decisions in
ADRs, and command semantics in versioned help. Link to executable registries instead of duplicating
their rules. `CONTEXT.md` is glossary-only: no implementation paths, decisions, or migration status.

Behavior changes update their owning help/metadata and user docs. In the final summary,
state whether docs or skills changed and why.
Behavior changes update owning help/metadata and user docs. Report docs or skill changes when relevant.
8 changes: 3 additions & 5 deletions docs/agents/cli-flags.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# Adding a CLI Flag

A new flag touches only the layers that need to understand it. Stop at the layer where it stops
mattering — threading it further is the common failure, not stopping too early.
Thread a flag only through the layers that consume it:

1. `packages/contracts/src/cli-flags.ts`: add to `CliFlags`; add the definition to the matching
`src/commands/cli-grammar/flag-definitions-*.ts` owner and the relevant group in `flag-groups.ts`
(for example `SNAPSHOT_FLAGS`). Then update the command family metadata/schema that exposes the
flag; find the owner with
`rg -n "<command>|supportedFlags|allowedFlags" src/commands src/cli-schema src/cli/parser`. For
schema-only CLI commands (`cdp`, `auth`, `connect`, `proxy`, `react-devtools`, `web`) the owner is
`SCHEMA_ONLY_CLI_COMMAND_SCHEMAS` in `src/cli-schema/command-overrides.ts`. New flags are
operator-only by default. Add a flag to `PROJECT_CONFIG_FLAG_KEYS` in
schema-only CLI commands, the owner is `SCHEMA_ONLY_CLI_COMMAND_SCHEMAS` in
`src/cli-schema/command-overrides.ts`. New flags are operator-only by default. Add a flag to `PROJECT_CONFIG_FLAG_KEYS` in
`src/cli-schema/cli-config.ts` only when repository control is safe; this positive allowlist is
the completeness gate.
2. `src/commands/cli-grammar/*`: read the CLI flag into command input.
Expand Down
50 changes: 29 additions & 21 deletions docs/agents/device-verification.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# Manual Device Verification

Read this before running `agent-device` by hand against a simulator, emulator, or physical device.
Read this for Apple runner changes or manual `agent-device` runs on simulators, emulators, or
physical devices. Live verification steps apply when exercising a device-facing path.

## Before the run: defeat staleness

Dev-loop staleness has three layers, and each produces a convincing false negative.
## Build freshness

- After changing runtime code reached through `bin/agent-device.mjs` or the daemon: `pnpm build`,
then `pnpm clean:daemon` — the daemon does not self-reload.
- Before any Android verification from source: `pnpm build`, `pnpm build:android`, `pnpm clean:daemon`.
`build:android` refreshes and verifies both bundled Android helper artifacts for the current
package version.
- `shutdown` deliberately HANDS OFF a healthy simulator runner. The adopted runner keeps serving the
old Swift binary until you kill its process or the source fingerprint changes, so "my change did
nothing" measured against an adopted runner is a classic false negative. If Swift runner code
changed, run `pnpm build:xcuitest`.
- `shutdown` hands off a healthy simulator runner; a new daemon may adopt the old binary. After
Swift runner changes, run `pnpm build:xcuitest` before verification. Use the session cleanup
procedure below if ownership is stuck.

## Prove the path under test was actually active

Expand All @@ -25,19 +23,30 @@ Dev-loop staleness has three layers, and each produces a convincing false negati
simulator, physical-device, Metro/dev-client, and app-surface steps. An already-installed
`com.callstack.agentdevicelab` is not sufficient — the README's Metro/dev-build and `snapshot -i`
checks must prove the expected app surface is running.
- For Android RN/Expo/dev-client apps on any local Metro port, `adb reverse tcp:<port> tcp:<port>` is
harmless and should be run before opening the app or URL.
- For Android RN/Expo/dev-client apps that use local Metro, configure
`adb reverse tcp:<port> tcp:<port>` for the app's Metro port before opening the app or URL.

## Session hygiene
## Worktree ownership and runner diagnostics

- Source-checkout daemon state is worktree-scoped, but devices are not. Use `pnpm daemon:state-dir`
to inspect it and different devices for concurrent worktrees.
- The first Node process after a newly signed Apple runner launches may block during Gatekeeper
verification. Warm it with a throwaway `node -e 0` before measuring.
- `DEVICE_IN_USE` has two flavors. "already in use by session X" is this daemon — follow its
`close --session` hint. "owned by session X in workspace Y" is another worktree's device
claim — non-retriable; run the error's `device status`/`device release --stale` recovery,
never PID hunting.

Every manually opened session is a resource that must be closed — including exploratory sessions and
failed verification attempts.
The OS-neutral Apple runner lives under `packages/platform-apple/src/runner/`. For connection errors,
retry policy, or command typing, start at `runner-contract.ts`; transport stays below session/client
behavior, and xctestrun build/cache logic stays outside request execution.

## Session hygiene

- Every `agent-device open` needs a matching `close` with the same `--session`, `--platform`,
`--udid`, and `--state-dir` before the agent finishes.
- Close manually opened sessions, including failed verification attempts, using their original
`--session`, `--platform`, `--udid`, and `--state-dir` values.
- Use a purpose-specific session name for experiments, and an isolated `--state-dir` under
`/private/tmp` when you need cleanup isolation beyond the current worktree's default daemon.
- Track opened sessions in working notes; close each one before the final response.
- If `close` is blocked or ownership looks stuck, inspect it with
`agent-device device status --stale` (daemonless), stop the owning daemon with
`agent-device daemon stop --state-dir <dir>` (add `--clean` to remove retained runners), and
Expand All @@ -48,8 +57,7 @@ failed verification attempts.

## Sandboxed environments

Start the daemon outside the sandbox with escalation. The daemon binds localhost, and sandboxed runs
fail before any product code executes, with `listen EPERM: operation not permitted 127.0.0.1` or
repeated `Failed to start daemon`/metadata cleanup messages. Those are not agent-device regressions —
rerun with escalation. Unit tests, typecheck, lint, and build can stay sandboxed unless they need
devices or listener access.
The daemon binds localhost. If the sandbox rejects the listener with `listen EPERM`, rerun with
host access when permitted. Generic `Failed to start daemon` or cleanup errors alone do not prove a
sandbox cause; inspect the underlying failure. Run other checks in the sandbox unless their tools
require host access.
11 changes: 2 additions & 9 deletions docs/agents/domain.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
# Domain Docs

Single-context repo. Before architecture, diagnosis, TDD, triage, PRD, or roadmap work, read
`CONTEXT.md` for domain vocabulary and the relevant ADRs in `docs/adr/`. Selector-capture work also
reads `docs/agents/selector-capture.md`.

Use `CONTEXT.md` vocabulary in issue titles, refactor proposals, test names, and architecture notes.
If a proposed change contradicts an ADR, say so explicitly and explain why the decision should be
reopened.

`AGENTS.md` routes to the rest of this directory by task type.
Use `CONTEXT.md` for domain vocabulary and `docs/adr/README.md` to locate decisions relevant to the
change. A proposed ADR contradiction requires an explicit decision update.

## Test-harness vocabulary

Expand Down
Loading
Loading