Skip to content

A standalone worker serves agent-device on localhost by default #957

Closed

Description

@V3RON

Part of #952.

Scope

A standalone worker serves agent-device on 127.0.0.1:4701 with no config set. On a worker with no gateway.url, http.enabled defaults to on, and agent-device is on unless capabilities.agent-device.enabled is set. With the profile from #956, an agent on the Mac connects and works with no operator step. A Mac without agent-device is not flagged as a problem. Joined workers and gateways keep today's defaults. The test harnesses keep HTTP and agent-device off unless a test asks for them. The docs drop the enable steps.

In short

After an upgrade, a standalone worker listens on 127.0.0.1:4701. The API and the console both still ask for a token. The worker also starts agent-device when the agent-device command is on PATH, and writes the profile from #956. A value the operator set wins. If agent-device was never set and is not installed, its capability is off: simlock doctor stays clean and the console shows no warning. Any other agent-device trouble stays loud, as today.

flowchart LR
  L[config load] --> J{gateway.url set?}
  J -->|no: standalone| D1["http.enabled default true; agent-device on unless set (changed)"]
  J -->|yes: joined| D2[both off, as today]
  D1 --> S{agent-device command found?}
  S -->|yes| R[daemons start; profile written]
  S -->|no, never set| O["capability off, no finding (changed)"]
  S -->|no, set true| U[unavailable + finding, as today]
Loading

Defaults:

Key Standalone worker today After Joined worker Gateway
http.enabled false true false (unchanged) true (unchanged)
capabilities.agent-device.enabled false on unless set false (unchanged) false (unchanged)

User session, a fresh standalone worker with agent-device on PATH and no config.json:

$ simlock daemon start
$ 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"

Today the connect fails, because there is no profile file and nothing listens on 4701. After this task the open leases and opens a simulator.

If this goes wrong, a user notices one of three things: the worker still needs config set first; a joined worker refuses to start with the http.enabled error; or doctor and the console complain on a Mac without agent-device.

Technical spec

Commit for every source: cac23e033f94ad87aee0b92e130190094df6d93a.

Facts

# Fact Source
F1 Every optional module's enabled defaults to false, forced after its own defaults; core does not name agent-device src/core/config.ts:894; src/core/AGENTS.md "Never imports"
F2 http.enabled defaults to role === "gateway"; a worker's port is 4701 and its host 127.0.0.1 src/core/config.ts:941-944
F3 Defaults, the file and overrides are merged; a worker then runs validateGatewayMembership, which refuses http.enabled true with gateway.url set, with ADR 0039's message src/core/config.ts:590-597, :800-808
F4 The module host reads capabilities.<name>.enabled: when it is false or the section is missing, nothing of the module loads and the capability is off ADR 0030 §2; src/agent-device/edge.test.ts:4271
F5 The module's worker part gets only its merged section (ctx.config) src/agent-device/worker.ts:110-130; src/modules/types.ts:131-133
F6 The supervisor finds the command on PATH at start; when it is not found the state is unavailable, reason not-found, and nothing retries until a restart. Other reasons: not-agent-device, wrong-version, state-dir, start-failed src/agent-device/host/agent-device-supervisor.ts:32, :269-270, :322-327; src/agent-device/AGENTS.md Availability
F7 agentDeviceFindings makes one module-unavailable doctor finding for any unavailable state; doctor findings carry no severity src/agent-device/host/availability-findings.ts:4-17; src/core/doctor.ts:131-134
F8 A capability is available, unavailable (with a reason) or off (no reason) src/contract/schemas.ts:1279-1290
F9 The console shows unavailable in warn tone and off as idle ui/src/model/worker-page.ts:467
F10 Plain simlock status on a worker prints no capabilities; simlock status --json (status.get) carries them src/cli/format.ts:33-40, :230
F11 An HTTP bind failure after a good start stops the daemon (http-start-failed), exit 1 src/daemon/main.ts:835; src/daemon/AGENTS.md
F12 e2e seedConfig has three bases: fake driver merges FAKE_LANE_BASE_CONFIG (no http, no capabilities), real driver merges only drivers.android, none merges nothing e2e/helpers/env.ts:90-97, :217-227
F13 Fast e2e tests set http.host/port without enabled (e.g. e2e/contract-surface.test.ts:110); real-driver slow files slow-ios-smoke, slow-ios-slim, slow-ios-os-range, slow-android-smoke, slow-warm-pool set no http grep -c 'http:' on each → 0
F14 The console lane writes http (enabled unless joined) and no capabilities e2e/console/fixtures.ts:239-255
F15 In-process tests call startDaemon( directly in src/agent-device/edge.test.ts, src/agent-device/gateway-front.test.ts, src/daemon/{optional-modules,server,main,gateway-modules}.test.ts, src/daemon/{lease-requires,gateway-fleet,stream-gateway,worker-capabilities}.e2e.test.ts and src/daemon/test-relay-support.ts; main.test.ts's start() sets no http and uses the real module registry grep -rl "startDaemon(" src; src/daemon/main.test.ts:91-114
F16 Tests asserting today's defaults: config.test.ts:123 (http: { enabled: false, … }), :1663 (worker HTTP off), definition.test.ts:35 (agent-device enabled false), edge.test.ts:4271 (no section → off) those lines
F17 The doctor seam that sees findings and the capability together is src/daemon/main.test.ts describe("doctor.run"), case "both daemons down: agent-device is found nowhere" src/daemon/main.test.ts:3319-3420, :3366-3372
F18 A launchd or systemd service saves PATH at install, so a service-run daemon finds the same agent-device src/ports/service-manager.ts:34-45
F19 Public pages state today's defaults or enable steps: reference/configuration.mdx:217, :307; worker/configuration.mdx:92, :113-116; worker/console.mdx:16, :27-31; using-devices/remote-agents.mdx:14-22; using-devices/agent-device.mdx:35-36; website/src/site/content.ts:334 the files at the commit
F21 config.get answers the loaded Config as is src/daemon/dispatcher.ts:879
F22 Until the supervisor's first start settles, the capability reads unavailable, reason starting src/agent-device/worker.ts:91-96, :145-150
F20 The slow-lane agent-device suite runs one shared daemon with HTTP and agent-device set on explicitly and mints a token per agent e2e/slow-agent-device.test.ts:136-146; e2e/helpers/agent-device.ts:99-171

Modules touched

Searches: http.enabled readers, enabled: false in defaultConfig, OptionalModuleDefinition, agentDeviceFindings, module-unavailable, startDaemon( (F15), seedConfig, http: in e2e/.

flowchart LR
  C["core/config.ts (changed: http default by gateway.url)"] --> H["modules host/types (changed: on-by-default for standalone, set-or-default bit)"]
  DEF["agent-device/definition.ts (changed: declares on-by-default)"] --> H
  H --> AD["agent-device worker + availability (changed: not-found by default → off)"]
  AD --> DR[core/doctor.ts]
  HR["e2e/helpers/env.ts, e2e/console/fixtures.ts, in-process test helpers (changed: pin off)"] --> C
Loading

Files: src/modules/types.ts (an optional definition may declare enabledByDefault: "standalone-worker", plain data), src/core/config.ts (after the merge: the http.enabled default follows gateway.url; for every module entry that declares enabledByDefault and whose enabled no layer sets, a worker with no gateway.url gets enabled: true; loadConfig's callers also get the set of module names whose enabled came from a default, outside Config; core never names agent-device), src/daemon/main.ts and src/modules/host.ts (pass that set to the module, e.g. enabledByDefault: boolean on WorkerContext), src/agent-device/definition.ts, src/agent-device/worker.ts and host/availability-findings.ts or agentDeviceCapabilitySource (not-found while on by default → off), e2e/helpers/env.ts, e2e/console/fixtures.ts, the F15 test helpers, e2e/slow-agent-device.test.ts and e2e/helpers/agent-device.ts, and the F16 tests. Manuals: src/http/AGENTS.md (State table), src/agent-device/AGENTS.md ("Off unless … (false)"), src/modules/AGENTS.md, the config.ts:937-940 comment. Website: the F19 pages.

Contract and event changes

None. config.get returns Config unchanged (src/daemon/dispatcher.ts:879), so it shows the resolved enabled (true on a standalone worker that never set it). The set of defaulted module names is not part of Config. No event.

Other code on the same state

Searches: readers of http.enabled (src/daemon/main.ts, requireJoinedWorkerHttpOff, requireGatewayHttp) and of capabilities.agent-device (module host, supervisor, doctor, status.get).

  • requireJoinedWorkerHttpOff (F3): the default is false when gateway.url is set, so a joined worker never trips it; an explicit true still does.
  • simlock config set writes only the key it is given, so the next start re-derives a default.
  • A worker that later joins (gateway.url set) has both off at its next start, and A standalone worker writes a private agent-device profile for local agents #956 withdraws the profile.
  • A standalone worker writes a private agent-device profile for local agents #956 writes the profile when HTTP is on and agent-device is turned on. After this task that includes a standalone Mac where agent-device is on by default but not installed: the profile and its token are written there too (ADR 0046 Consequences), and do nothing until agent-device is installed and the worker restarts.

Failure modes

Call Outcome What happens Limit, counted from
HTTP bind on 4701 ok listening none
same taken (EADDRINUSE) daemon stops, http-start-failed, exit 1, as with HTTP on today (F11) none
agent-device lookup found daemons start as today 15 s ready wait (today)
same not found, never set capability off (both roles), no doctor finding none
same not found, set true unavailable, today's finding none
same wrong version, not agent-device, state dir, start failed, never set unavailable, today's finding none

This task writes no state. Two daemons on one machine with default HTTP clash as in the taken row, which is why the harnesses pin HTTP off.

Rules in play

  • ADR 0046 §1: the defaults, the set-or-default bit outside the contract, and quiet only for not-found, exactly as written. It narrows ADR 0023 §1 and ADR 0030 §2 for this module only.
  • ADR 0039 "invalid … gateway.url set and http.enabled: true": kept; a default never produces it.
  • ADR 0011 §3 "the console is served whenever HTTP is": the console now opens on a standalone worker by default and still asks for a token.
  • Architecture: core names no agent-device; the on-by-default flag is a neutral member of the module definition.
  • Documentation rule 9: the F19 pages change in this PR.
  • Slow lane only where the fast lane cannot prove it: one case, the real CLI with the profile.

Tests

Seams: loadConfig (model src/core/config.test.ts); a whole worker daemon (src/daemon/main.test.ts describe("doctor.run") for findings, src/agent-device/edge.test.ts "a worker's capabilities in status.get" for the capability); slow lane e2e/slow-agent-device.test.ts.

  • A worker with no config file loads with http.enabled true. Cases: standalone → true; joined (gateway.url and gateway.token set) → false; gateway → true; standalone with http.enabled: false in the file → false; joined with http.enabled: true in the file → refused with ADR 0039's message. Extends config.test.ts:123, :1663 and :1835.
  • A standalone worker with no capabilities.agent-device section runs agent-device; a joined worker and a gateway do not. Cases: no section, standalone → available; enabled: false → off; no section, joined → off. Extends edge.test.ts:4271 and definition.test.ts:35.
  • agent-device trouble while on by default (doctor.run, extends the main.test.ts:3366 cases): not found, never set → no agent-device finding, capability off; not found, set true → today's finding, unavailable; wrong version, never set → today's finding, unavailable.
  • A standalone worker on its defaults serves agent-device end to end (in-process startDaemon with the fake agent-device, model edge.test.ts): config sets only http.port (a free port) and capabilities.agent-device.command (the fake), no enabled keys → HTTP listens, config.get shows both enabled true, the A standalone worker writes a private agent-device profile for local agents #956 profile is written, and GET /v1/agent-device/health with the profile's token answers 200. The same with gateway.url and gateway.token set → no HTTP, no profile, capability off.
  • The harnesses give HTTP and agent-device to no test that does not ask: the fake and real seedConfig bases and the console lane config set http.enabled: false and capabilities.agent-device.enabled: false, which a test's own values override; every F15 helper does the same. A fast e2e test that sets http.port and uses HTTP sets enabled: true. The fast e2e files the change touches, and the F15 files, pass.
  • Slow lane, e2e/slow-agent-device.test.ts: a new case starts the pinned agent-device CLI with no AGENT_DEVICE_DAEMON_AUTH_TOKEN and runs connect --remote-config <data dir>/agent-device-profile.json --session <name>, open --platform ios, install of the fixture app (an upload) and close, each with --session. The lease's owner is the profile token. The existing token cases keep passing on the same daemon.

Done when

  • pnpm exec vitest run --project unit <file> passes for src/core/config.test.ts, src/agent-device/definition.test.ts, src/agent-device/edge.test.ts and src/daemon/main.test.ts with the lines above. The doctor.run cases wait for the capability to leave starting before reading it.
  • pnpm test:e2e passes for the e2e files the harness change touches. CI is green.
  • Slow lane: scripts/slow-e2e.sh e2e/slow-agent-device.test.ts passes, including the profile case. It needs the "agent-device through simlock" machine.
  • The website build passes. using-devices/agent-device.mdx says a standalone worker serves agent-device by default, drops the config set steps, and says to restart the worker after installing agent-device. The other F19 pages state the new defaults.

Out of scope

Depends on

Written by an agent.

Activity

  1. added
    task:draftScope written; technical spec or its spec check missing.
    on Oct 23, 2026
  2. added
    task:readySpec checked. An agent may implement it once its Depends on allow.
    and removed
    task:draftScope written; technical spec or its spec check missing.
    on Oct 23, 2026
  3. self-assigned this
    on Oct 23, 2026
  4. added a commit that references this issue on Oct 24, 2026
    a8de7d3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

task:readySpec checked. An agent may implement it once its Depends on allow.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions