Skip to content

feat: load hosted agent drivers through a contract package - #3686

Merged
janicduplessis merged 6 commits into
mainfrom
feat/pluggable-agent-drivers
Oct 13, 2026
Merged

janicduplessis merged 6 commits into
mainfrom
feat/pluggable-agent-drivers

Conversation

@janicduplessis

@janicduplessis janicduplessis commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Description

stim-server's hosted agent driver is selected by a hard-coded hosting.agentDriver === 'agent-device' check. agent-device's name is also baked into the grant union in core, the client's remote config writer and the archived action reader. Adding a second tool means editing all of them. Design: docs/specs/2026-10-13-pluggable-agent-drivers-design.md.

Solution

  • @stim-cli/agent-driver is a contract-only package, modeled on @stim-cli/cache:
    • A driver exports apiVersion: 1, id and createAgentDriver({ env, stateDir, claimRoot, device }), which returns the existing HostedAgentDriver. The host keeps every policy decision: grants, the tailnet node pin, restarts and teardown.
    • Two optional capabilities: client turns a grant into a 0600 file plus a command that never carries the token, and actions reads the driver's recorded actions.
    • The loaders never throw: a failed or slow import, a wrong apiVersion or id, a module whose checks throw, or a throwing factory comes back as unavailable. A factory must return the driver itself; one that returns a promise is refused.
    • runAgentDriverContract is a test kit for driver authors.
  • @stim-cli/agent-driver-agent-device holds the old agent-device-driver.ts, moved without behavior changes. Its client writes the same agent-device-remote.json, and its actions reader reads agent-device/sessions as before, so existing archives still read.
  • Registries in stim-server (src/agent-drivers.ts) and in the CLI replace the hard-coded check.
    • The grant and access payloads in core accept any lowercase driver name.
    • HOSTED_AGENT_DRIVERS is still ['none', 'agent-device'].
    • An unknown configured driver leaves the app on none with a notice.
    • Stopping a session tears down the driver whose claim root that session has, not whatever the setting says now.
    • The CLI lists each first-party driver's client config file (agent-device-remote.json) statically. Removing access deletes those files even when a driver package fails to load, as main did, so a broken install never leaves a token file behind.
  • The two packages are added to the release workflow, release-prep, runtime-floor, typecheck, the ratchet and the Desktop server bundle.

Argent

Argent (@swmansion/argent 0.28.0) is researched and not shipped. The design doc has the details. Its transport fits: token-checked HTTP, loopback-only Host, and a remote client through ARGENT_TOOLS_URL. It can't be confined to one hosted device, though:

  • Its tool-server always starts a watcher that injects DYLD_INSERT_LIBRARIES into every booted simulator in the default set. Stim's simulators live in that set, so one hosted session would change other clients' simulators and the user's own.
  • It has no daemon-side device or command policy. list-devices and boot-device reach the whole Mac.

Once Argent can serve a single device, adding it is one package plus its name in the two registries and in HOSTED_AGENT_DRIVERS.

Risk

  • With refactor: move Tailscale behind a transport contract #3685 merged, the server's checkout update carries 9 packages. refactor: move Tailscale behind a transport contract #3685 raised the receiver's limit from 8 to 16, but remote Macs on older releases still refuse more than 8 in a checkout update; release updates are unaffected.
  • Argent and other automation tools still work with devices on the same Mac; the settings docs now say they are not hosted drivers.
  • Before the next release tag, @stim-cli/agent-driver and @stim-cli/agent-driver-agent-device need placeholder publications and trusted publishers. Follow "First publication of a NEW package" in docs/release-recovery.md.

Test plan

  • Contract kit run against agent-device in packages/agent-driver-agent-device/__tests__/contract.test.ts. The moved driver tests are in __tests__/driver.test.ts.
  • Loader cases in packages/agent-driver/__tests__/loader.test.ts: a BigInt apiVersion, a throwing getter, a factory returning a rejected promise (no unhandled rejection), and the kit failing a driver that reports a missing tool as a plain error, or whose forward() throws or never answers.
  • packages/stim-cli/src/__tests__/hosted-agent-access.test.ts: removing access deletes agent-device-remote.json when the agent-device package fails to import.
  • packages/server/__tests__/agent-drivers.test.ts: an unknown hosting.agentDriver gives none with a notice. Stopping a session uses the claim root's driver after the setting changes.
  • Not run: the agent-device-*.compat.test.ts files, whose imports changed, because there was no device hardware; the Desktop server-runtime.mjs bundle; and the full e2e suite.

Fixes #3682

@janicduplessis janicduplessis added area: cli packages/stim-cli, including its README and guide area: core packages/core area: server packages/server area: desktop apps/desktop area: docs docs/ and root documents area: repo .github/, scripts/, test/, packaging/, patches/, root config labels Oct 13, 2026
@janicduplessis janicduplessis added the review: needs-human Holds merge until a maintainer reviews the lines named in the Human review: comment label Oct 13, 2026
@janicduplessis

Copy link
Copy Markdown
Collaborator Author

Human review: unapproved changes in protected areas at f8a29b2.

@janicduplessis

Copy link
Copy Markdown
Collaborator Author

Human review: unapproved changes in protected areas at 0adbc03.

  • .github/workflows/release.yml:52 packs two new packages in the publish job, which runs under environment: release with id-token: write.
  • .github/workflows/release.yml:165-180 adds new npm publish --provenance steps for @stim-cli/agent-driver and @stim-cli/agent-driver-agent-device in that job. Each needs a placeholder publication and a trusted publisher before the next tag.
  • packages/server/src/agent-drivers.ts:13-76 is the registry that picks which driver code stim-server loads and runs to serve another machine's device-host grant, plus the stop-time choice of driver by its claim root.
  • packages/agent-driver/driver.ts:149-196 is the dynamic-import loader and factory call that runs that driver code inside stim-server. Changed since the previous comment: the loader now catches errors thrown by its own module checks, and a factory that returns a promise is refused.
  • packages/server/src/server.ts:762-771 wires hosting.agentDriver to the registry for hosted macOS apps and hosted device sessions.

@janicduplessis janicduplessis added the review: ai-approved A fresh AI review found nothing left to fix label Oct 13, 2026
Add @stim-cli/agent-driver, the contract stim-server uses to start the
tool a client's coding agent drives hosted apps with: apiVersion 1, id,
createAgentDriver, optional client and actions capabilities, a loader
that never throws, and runAgentDriverContract.

Move the agent-device adapter into @stim-cli/agent-driver-agent-device
with no behavior change. stim-server and the CLI resolve drivers from a
registry of the first-party packages they depend on instead of comparing
hosting.agentDriver to agent-device. Grants and access records carry any
driver name; a lease stays validated when present. Archived agent actions
come from each driver's actions reader.

Refs #3682
A session no registered driver ran in no longer fails to stop when
hosting.agentDriver names an unknown driver. Tests cover the notice for an
unknown setting and teardown by the claim root a session has.

Refs #3682
Remove every first-party driver's client config file from a static list,
so a driver package that does not load cannot leave its token file
behind. The module loader catches errors from its own checks, and the
driver loader refuses a factory that returns a promise. The contract
kit turns a throwing or silent forward() and a listener error into a
failed check and closes its connections.

Refs #3682.
@janicduplessis
janicduplessis force-pushed the feat/pluggable-agent-drivers branch from 0adbc03 to d54ebbd Compare October 13, 2026 15:18
@janicduplessis janicduplessis removed the review: ai-approved A fresh AI review found nothing left to fix label Oct 13, 2026
@janicduplessis

Copy link
Copy Markdown
Collaborator Author

Human review: unapproved changes in protected areas at d54ebbd.

  • .github/workflows/release.yml:52 packs two new packages in the publish job, which runs under environment: release with id-token: write. Changed since the previous comment: rebased onto refactor: move Tailscale behind a transport contract #3685, so the list now also carries transport transport-tailscale; the agent-driver packages sit before stim-cli and server.
  • .github/workflows/release.yml:165-180 adds new npm publish --provenance steps for @stim-cli/agent-driver and @stim-cli/agent-driver-agent-device in that job, before stim, the transports and @stim-cli/server. Each needs a placeholder publication and a trusted publisher before the next tag.
  • packages/server/src/agent-drivers.ts:13-76 is the registry that picks which driver code stim-server loads and runs to serve another machine's device-host grant, plus the stop-time choice of driver by its claim root. Unchanged since the previous comment.
  • packages/agent-driver/driver.ts:149-196 is the dynamic-import loader and factory call that runs that driver code inside stim-server. Unchanged since the previous comment.
  • packages/server/src/server.ts:795-804 loads every first-party driver's action reader at startup and wires hosting.agentDriver to the registry for hosted macOS apps and hosted device sessions. Moved from 762-771 by the rebase; content unchanged.

@janicduplessis janicduplessis added the review: ai-approved A fresh AI review found nothing left to fix label Oct 13, 2026
@janicduplessis
janicduplessis marked this pull request as ready for review October 13, 2026 15:22
@janicduplessis
janicduplessis added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 6e99b1c Oct 13, 2026
32 of 34 checks passed
@janicduplessis
janicduplessis deleted the feat/pluggable-agent-drivers branch October 13, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli packages/stim-cli, including its README and guide area: core packages/core area: desktop apps/desktop area: docs docs/ and root documents area: repo .github/, scripts/, test/, packaging/, patches/, root config area: server packages/server review: ai-approved A fresh AI review found nothing left to fix review: needs-human Holds merge until a maintainer reviews the lines named in the Human review: comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pluggable hosted agent drivers, with argent support

1 participant