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
4 changes: 3 additions & 1 deletion packages/daemon/src/resources/lease-registry-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ export function normalizeLeaseAdmissionRequest(
const runId = normalizeRunId(request.runId);
if (!runId) throw new AppError('INVALID_ARGS', 'tenant isolation requires run id.');
const leaseId = normalizeLeaseId(request.leaseId);
if (!leaseId) throw new AppError('INVALID_ARGS', 'tenant isolation requires lease id.');
if (!leaseId) {
throw new AppError('INVALID_ARGS', 'tenant isolation requires lease id from leases.allocate.');
}
return { ...request, tenantId, runId, leaseId, leaseBackend };
}
function normalizeOptionalLeaseScope(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ test('close on a lease-less but stored tenant-isolated session still requires a
registry,
session,
),
/tenant isolation requires lease id/,
/tenant isolation requires lease id from leases\.allocate/,
);
});

Expand All @@ -84,7 +84,7 @@ test('close with an app target still requires a lease id even with no lease anyw
registry,
undefined,
),
/tenant isolation requires lease id/,
/tenant isolation requires lease id from leases\.allocate/,
);
});

Expand Down Expand Up @@ -123,7 +123,7 @@ test('non-close commands on a tenant-isolated session still require a lease id',
registry,
undefined,
),
/tenant isolation requires lease id/,
/tenant isolation requires lease id from leases\.allocate/,
);
});

Expand All @@ -143,7 +143,7 @@ test.each(['bogus', 'proxy', 'browserstack'])(
registry,
undefined,
),
/tenant isolation requires lease id/,
/tenant isolation requires lease id from leases\.allocate/,
);
},
);
Expand Down
30 changes: 30 additions & 0 deletions website/docs/docs/limrun.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,36 @@ const runtime = new LimrunRuntime({

To drive existing instances, pass `instances: { ios: { apiUrl, token } }` (Android also takes `adbUrl`), with or without `apiKey`. The runtime never creates or deletes instances for a platform listed there.

A provider that reaches a harness-owned instance through the Node.js client allocates a Limrun lease in `acquire` and releases that same lease in `release`. Put the returned `lease.leaseId` on the client scope, and pass that same id to `leases.release`. Start the daemon with the instance URL and token (`LIM_IOS_INSTANCE_URL` and `LIM_IOS_INSTANCE_TOKEN`). `leases.allocate` attaches to that instance. `leases.release` returns `{ released, provider }`. Neither call creates or deletes the instance. Allocate with `retainOnClose: true` because the provider releases the lease itself: without it, a session `close` before `release` ends the lease and `leases.release` returns `released: false`. A retained lease still expires after its `ttlMs`, so keep heartbeating it between sessions.

```ts
import { createAgentDeviceClient } from 'agent-device';

const scope = {
tenant: 'limrun',
runId: process.env.GITHUB_RUN_ID ?? 'local-run',
leaseBackend: 'ios-instance',
leaseProvider: 'limrun',
} as const;

const provider = {
acquire: async (req: { slot: string }) => {
const lease = await createAgentDeviceClient({ session: `lease-${req.slot}` }).leases.allocate({
...scope,
platform: 'ios',
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
retainOnClose: true,
});
return {
id: lease.leaseId,
client: { ...scope, leaseId: lease.leaseId, sessionIsolation: 'tenant' as const },
};
},
release: async (lease: { id: string }) => {
await createAgentDeviceClient({}).leases.release({ ...scope, leaseId: lease.id });
},
};
```

After allocating a lease, call `runtime.getDeviceSession(device)` to get the allocated device's capabilities: app inventory, foreground state where Limrun exposes it, key input, bounded log reads, recording, remote asset installation, and the interactor. On Android it also exposes agent-device's `AndroidAdbProvider` for helpers and reversible port forwarding. On iOS it exposes a typed `simctl` execution handle so your bridge can own runner lifecycle and launch policy. The runtime does not expose raw Limrun clients.

## Get artifacts and troubleshoot
Expand Down
Loading