Remotes
A Remote extends a Session onto hardware you control. It serves a confined
work/ folder, file operations, shell commands, and optional custom tools.
The Agent loop, model credentials, durable transcript, approvals, and cloud-side files remain in Skopik Cloud. A Remote is reach into a machine; it does not move the Agent's reasoning loop onto that machine.
Start a Remote from the CLI
Install the CLI, supply an API key, and serve a root directory:
npm install -g @skopiklabs/cli
export SKOPIK_API_KEY="sk_live_..."
skopik remote up \
--label "Build Mac" \
--root "$PWD/skopik-workspaces" \
--max-concurrent-sessions 2
The command registers this installation, stores its Remote credentials for
restart, sends heartbeats, polls for assignments, and serves until stopped.
When --root is omitted, the default is ~/.skopik/workspaces.
Use --readonly when Sessions should be limited to reading the served
workspace:
skopik remote up --label "Read-only source" --readonly
Bind a Session
List online Remotes and select one while opening the Session:
const { data: remotes } = await skopik.remotes.list()
const remote = remotes.find((candidate) => candidate.status === 'online')
if (!remote) throw new Error('No Remote is online')
const session = await skopik.sessions.open({
agent: agentId,
remote: remote.remoteId,
prompt: 'Inspect the repository, run the test suite, and fix the failing test.',
permission: 'ask',
})
curl "https://api.skopik.com/api/v1/sessions" \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"agent\": \"$AGENT_ID\",
\"remote\": \"$REMOTE_ID\",
\"prompt\": \"Inspect the repository, run the test suite, and fix the failing test.\",
\"permission_mode\": \"ask\"
}"
Open verifies that the Remote is online and enabled. A Remote-bound Session's
work/ tree is created below:
{root}/sessions/{sessionId}/work/
Each Session gets a confined folder. The Agent sees the same logical filesystem regardless of location.
File durability
Remote work/ is local reach and is not mirrored to cloud storage. Treat a
git remote or another trusted system as the durable source for code. Put
deliverables that must survive the machine into the Session's cloud-side
output/ tree.
| Session path | Location with a Remote |
|---|---|
input/ |
Skopik Cloud |
notes/ |
Skopik Cloud |
work/ |
Remote machine |
output/ |
Skopik Cloud |
Build a custom Remote
Use @skopiklabs/sdk/remote when your product should host the serving loop or
expose machine-local tools.
npm install @skopiklabs/sdk zod
import { createRemote, createRemoteTool } from '@skopiklabs/sdk/remote'
import { z } from 'zod'
const gitStatus = createRemoteTool({
name: 'git_status',
description: 'Return git status for the Session workspace.',
readonly: true,
inputSchema: z.object({}),
execute: async (_input, context) => {
const result = await context.workspace.exec('git status --short')
return {
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
}
},
})
const remote = createRemote({
label: 'Build Mac',
root: '/srv/skopik-workspaces',
maxConcurrentSessions: 2,
auth: { apiKey: process.env.SKOPIK_API_KEY },
tools: [gitStatus],
})
remote.on('setup', (context) => {
context.log(`Workspace ready at ${context.workspace.dir}`)
})
await remote.start()
start() resolves when the Remote is registered and online; the serving loop
continues in the background. Call await remote.stop() during graceful
shutdown so active serving windows can drain.
Custom tool policy
Every custom tool declares:
| Field | Meaning |
|---|---|
name |
Stable tool name using letters, digits, _, or -. |
description |
When the Agent should use the tool. |
inputSchema |
Zod schema validated on the machine. |
readonly |
Whether the tool is allowed under Remote read-only posture. |
needsApproval |
Whether every call must pass the durable approval gate. |
execute |
Machine-local implementation. |
Custom tools are additive. They cannot replace the built-in file and shell operations.
Manage Remotes
const { data, page } = await skopik.remotes.list({ limit: 50 })
const remote = await skopik.remotes.get(data[0].remoteId)
const updated = await skopik.remotes.update(remote.remoteId, {
displayName: 'Build Mac — rack 2',
enabled: true,
maxConcurrentSessions: 3,
})
await skopik.remotes.delete(remote.remoteId)
Deleting a Remote revokes its machine credential and is rejected while the Remote has active or queued Session work.
Remote status is offline, connecting, online, busy, paused, or
error. enabled: false is the trust switch: it prevents new Sessions even
if the machine is still sending heartbeats.
Low-level registration
Most hosts should use the CLI or Remote SDK. If you are implementing the wire
yourself, remotes.register() creates the Remote and returns the machine's
registration token once:
const { remote, registrationToken } = await skopik.remotes.register({
name: 'build-mac',
displayName: 'Build Mac',
hostKind: 'external',
installId: 'device-8f912',
maxConcurrentSessions: 2,
readonly: false,
})
The machine then authenticates with that registration token, sends paced
heartbeats, and long-polls remotes.poll() for serving assignments. Store the
token like a password; it is distinct from the API key used for enrollment.
Environments on Remotes
An Environment can define Remote-compatible setup and teardown scripts, plain variables, governed actions, and machine-local secret aliases. Select both resources when opening the Session:
await skopik.sessions.open({
agent: agentId,
remote: remoteId,
environment: environmentId,
prompt: 'Install dependencies and run the integration suite.',
})
The Remote prepares the Session folder, applies the captured Environment version, serves Agent operations, runs teardown, and releases the serving window.
API reference
| SDK | HTTP | Purpose |
|---|---|---|
remotes.register() |
POST /remotes |
Register a machine and return its token. |
remotes.list() |
GET /remotes |
List Remotes. |
remotes.get() |
GET /remotes/{idOrSlug} |
Read one Remote. |
remotes.update() |
PUT /remotes/{idOrSlug} |
Change display, enablement, capacity, or status. |
remotes.delete() |
DELETE /remotes/{id} |
Revoke and remove an idle Remote. |
remotes.heartbeat() |
POST /remotes/{id}/heartbeat |
Report machine liveness and capabilities. |
remotes.poll() |
GET /remotes/poll |
Claim a serving assignment with machine auth. |