Skip to content

agent-device on the same Mac works with no operator step #952

Open
2 of 2 issues completed
Open

Description

@V3RON

Request: #950

Problem

agent-device is the primary way agents are meant to use Simlock, and it is the path with the most setup. Before the first agent-device open on the same Mac as the worker, an operator turns on capabilities.agent-device.enabled and http.enabled, restarts the daemon, and mints one agent token per agent. The agent exports the token and runs connect proxy against a URL the docs never state for localhost. A plain simlock lease needs none of this: the local socket identifies the user and the Claude Code or Codex session id identifies the agent.

Who it is for

A developer with one Mac running a standalone Simlock worker, whose coding agents (Claude Code, Codex, any CLI agent) drive simulators and emulators through the agent-device CLI on that same Mac.

Outcome

On a standalone worker with agent-device installed, an agent connects with one command that names a private file, then opens, uses and closes a device. Nobody runs a config set, restarts the daemon or mints a token first. Only the Mac user who runs the worker can read that file. Remote agents, joined workers and gateways work as today.

How it works for the user

flowchart TD
  S[simlock daemon start, standalone worker] --> H[HTTP on 127.0.0.1:4701, agent-device on]
  H --> P[writes ~/.simlock/agent-device-profile.json, readable by this user only]
  A[Agent, same user] -->|agent-device connect --remote-config profile --session id| C[connected]
  C -->|open / install / close, each with --session| D[device leased and driven]
  X[Other account or a browser page] -.cannot read the file.-> E[401 as today]
  N[agent-device not installed] --> Q[capability off, doctor quiet]
Loading

Before (the operator turned both settings on, restarted, and ran simlock token create --role agent):

$ export AGENT_DEVICE_DAEMON_AUTH_TOKEN=slk_…
$ agent-device connect proxy --daemon-base-url http://127.0.0.1:4701/v1/agent-device
$ agent-device open --platform ios

After (a fresh standalone worker, agent-device on PATH, no operator step):

$ agent-device connect --remote-config ~/.simlock/agent-device-profile.json --session "$CLAUDE_CODE_SESSION_ID"
$ agent-device open --platform ios --session "$CLAUDE_CODE_SESSION_ID"
$ agent-device install ./MyApp.app --session "$CLAUDE_CODE_SESSION_ID"
$ agent-device close --session "$CLAUDE_CODE_SESSION_ID"

All local agents share the profile's token. What keeps them apart is the agent-device session, so every command passes the agent's own --session. agent-device 0.21.28 runs a command with no --session flag in the session that connected last, which may be another agent's (dist/src/cli.js, allowActiveFallback; #931).

After the profile token is revoked and the worker restarts with a new one, agent-device answers Active remote connection config changed. Run agent-device connect --force to refresh it., and the agent reconnects with --force.

Examples

Defaults:

Key Standalone worker today After Joined worker Gateway
http.enabled false true (127.0.0.1:4701) false (unchanged) true (unchanged)
capabilities.agent-device.enabled false on unless set false (unchanged) false (unchanged)
<data dir>/agent-device-profile.json none written when HTTP and agent-device are on none none

agent-device on a standalone worker that never set capabilities.agent-device.enabled:

agent-device on the Mac Today After
installed, right version off available
not on PATH off off, no doctor finding
on PATH, too old off unavailable, doctor finding
set enabled: true, not on PATH unavailable, doctor finding unavailable, doctor finding

What could go wrong

  • Something else already listening on port 4701 stops the worker from starting after the upgrade, as an explicit http.enabled: true does today. The operator sets http.port or turns HTTP off.
  • Local agents see each other's leased devices in agent-device devices. An agent that leaves --session off a command, or reuses another's name, drives the other agent's device, and nothing refuses it.
  • Any program running as the same Mac user can read the profile and act as a local agent. The Unix socket already has the same boundary.
  • Revoking the profile token cuts every local agent off until the next worker start.

Words used

  • standalone worker: a worker daemon with no gateway.url, e.g. a developer's Mac. A joined worker has gateway.url set.
  • profile: agent-device's remote-config file (--remote-config), here ~/.simlock/agent-device-profile.json. It holds the worker's agent-device URL and a token.
  • profile token: the agent token the worker keeps in the profile, labelled agent-device on this Mac.
  • session: agent-device's --session name. Each agent picks its own, e.g. its Claude Code session id.

Non-goals

Completion conditions

  • A standalone worker with HTTP and agent-device on writes the profile at mode 0600, keeps the same bytes across restarts, and removes it when joined or turned off.
  • With the profile, a real agent-device CLI with no token in its environment connects, opens an iOS simulator, installs an uploaded app and closes it (slow lane).
  • A worker with no config.json and no gateway.url starts with HTTP on and agent-device on. A joined worker starts with both off.
  • A standalone worker without agent-device on PATH starts, and simlock doctor lists no agent-device finding.
  • The agent-device docs page gives the profile connect line, --session on every command, and the connect --force remedy, with no config set steps.

Open questions

none

Decisions

Shared values

Value or state Rule Written by Read by Source
Standalone worker a worker with no gateway.url today's config #956 (write or withdraw), #957 (defaults) src/core/config.ts:800-806
Profile file <data dir>/agent-device-profile.json, mode 0600, atomic; content and key order as ADR 0046 §2 (a wildcard host → 127.0.0.1); written by the module host after the socket claim and before HTTP listens, whenever HTTP is on and agent-device is turned on (set or by default, found or not); deleted otherwise; rewritten only when its bytes change #956 agent-device CLI; #957 defaults test and slow-lane case ADR 0046 §2–3
Profile token an agent token labelled agent-device on this Mac; reused while it verifies; every other token with the label is revoked at each start, so at most one is live #956 /v1/agent-device (today's token check), token.list ADR 0046 §3
Profile prepare failure warn Could not prepare the agent-device profile: <message>; the start goes on #956 #956 tests ADR 0046 §3
http.enabled default true on a standalone worker, false on a joined one, true on a gateway; a set value wins #957 daemon start, #956 (when to write) ADR 0046 §1; ADR 0039
agent-device on by default the definition declares it; the config loader sets enabled: true on a standalone worker when no layer sets it, so config.get shows true; the names of defaulted modules travel beside Config to the module #957 module host, the agent-device module, #956 (when to write) ADR 0046 §1
Quiet not-found not found while on by default → capability off, no doctor finding; every other failure, or not-found when set, → unavailable with today's finding #957 doctor, status.get, console ADR 0046 §1

Behaviours

Behaviour (invoker, action, what it sees) Seam Cases Task
A worker with HTTP and agent-device on starts and writes the profile; a request with its token reaches agent-device in-process worker startDaemon with the fake agent-device (src/agent-device/edge.test.ts) mode 0600; content and order; URL for host 127.0.0.1, 0.0.0.0, ::, ::1; one labelled agent token; allocate granted, owner the token id #956
The worker restarts same nothing changed → same bytes and token; port changed → URL rewritten, same token; token revoked, file deleted, file not JSON, token unlabelled → new token, others revoked #956
The profile does not apply same HTTP off (always so on a joined worker), agent-device off → no file, no labelled token; gateway → none #956
Preparing the profile fails same write refused → worker starts, one warn line #956
A worker loads its config with no http.enabled loadConfig (src/core/config.test.ts) standalone → true; joined → false; gateway → true; explicit false kept; joined with explicit true → ADR 0039's refusal #957
A worker with no capabilities.agent-device section whole worker (edge.test.ts status.get) standalone → available; enabled: false → off; joined → off #957
agent-device trouble while on by default src/daemon/main.test.ts doctor.run not found, never set → off, no finding; not found, set → finding; wrong version, never set → finding #957
A standalone worker with no enabled keys serves agent-device end to end in-process startDaemon (edge.test.ts) standalone → HTTP listens, profile written, health with the profile token 200; joined → none of these #957
A test harness starts workers e2e/helpers/env.ts, e2e/console/fixtures.ts, in-process helpers no test gets HTTP or agent-device without asking #957
A real agent-device CLI uses the profile slow lane e2e/slow-agent-device.test.ts connect --remote-config, open --platform ios, install (upload), close, each with --session, no token in the environment #957

Tasks

flowchart LR
  T1["#956 private agent-device profile"] --> T2["#957 on by default"]
Loading

Completion conditions:

Open work in the same area:

Written by an agent.

Activity

  1. added
    feature:specSpec in progress, business and technical together.
    on Oct 23, 2026
  2. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Spec updated: Who it is for, Outcome, How it works, Examples, Decisions (ADR 0046, PR #954), Shared values, Behaviours and Tasks (#956, #957) added.

    Written by an agent.

  3. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Spec updated: the trust model changed from a no-token loopback identity to a private agent-device profile file (ADR 0046 rewritten). How it works, Examples, What could go wrong, Words used, Non-goals, Completion conditions, Shared values, Behaviours and Tasks rewritten; #956 retitled and rewritten; #957 rewritten.

    Written by an agent.

  4. added
    feature:plannedSplit into tasks. Never picked up itself.
    and removed
    feature:specSpec in progress, business and technical together.
    on Oct 23, 2026
  5. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: none. The maintainer asked to stop before the gate. The stack #954 → #963 (#956) → #967 (#957) is ready, reviewed, green and up to date with main.
    Parked: none
    Waiting: the stack's gate (merge-pr.sh 967), on the maintainer
    Decided: #956 — must the profile be on disk before HTTP listens? → yes, pinned by a test; #957 — the profile needs a clientId (agent-device's proxy open refuses one without it) → a constant id
    Found: #971 — the agent-device sharding slow-lane case fails on main too (not caused by this feature)

    Written by an agent.

  6. github-actions commented on Oct 24, 2026

    @github-actions

    All 2 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

  7. github-actions commented on Oct 24, 2026

    @github-actions

    All 2 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature:plannedSplit into tasks. Never picked up itself.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions