skopik

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',
})

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.