Remotes
A remote is a mount — a folder on a customer machine served into remote-bound sessions, plus any custom tools the host advertises. Skopik cloud still runs the agent; the remote serves files and tool calls from your machine into that work, keeping private files and local tools close without shipping them to the cloud.
Work stays local by design: a remote-bound session's work/ folder lives at {root}/sessions/{sessionId}/work/ on your machine and is never mirrored to cloud storage — no checkpoint archives, no background sync. Durable results leave through the channels you already trust: a git push, or the session's cloud-side output/ folder. When a session is canceled, the remote receives a cleanup assignment and deletes its local session folder; completed sessions keep theirs.
You use the SDK to register and manage remotes. The host side — long-polling for assignments, sending heartbeats, and serving operations — is handled for you by the Skopik desktop app, the CLI, or the Remote SDK (@skopiklabs/remote-sdk). remotes.heartbeat() and remotes.poll() are documented below for hosts built directly on the client.
Quick Links
| Action | Description |
|---|---|
remotes.register() |
Register a desktop, CLI, or external remote. |
remotes.list() |
List remotes. |
remotes.get() |
Read remote details. |
remotes.update() |
Update remote policy, status, or capacity. |
remotes.heartbeat() |
Report host liveness and capabilities. |
remotes.poll() |
Long-poll for the next assignment. |
Common Flow
Register a remote with a workspace API key, then bind a session to it. The session's work/ folder is served from the remote's machine while the session runs — any session shape can bind, one-shot jobs and long-lived assistants alike.
const { remote, registrationToken } = await client.remotes.register({
hostKind: 'cli',
label: 'Jordan laptop',
installId: 'jordan-mbp-001',
maxConcurrentTasks: 1,
})
const { session } = await client.sessions.open({
agent: 'mike',
prompt: 'Review the mounted folder and summarize anything stale.',
remote: remote.remoteId,
})
registration = client.remotes.register(
host_kind="cli",
label="Jordan laptop",
install_id="jordan-mbp-001",
max_concurrent_tasks=1,
)
session = client.sessions.open(
agent="mike",
prompt="Review the mounted folder and summarize anything stale.",
remote=registration.remote.remote_id,
).session
curl "$SKOPIK_API_BASE/remotes/register" \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"host_kind": "cli",
"label": "Jordan laptop",
"install_id": "jordan-mbp-001",
"max_concurrent_tasks": 1
}'
curl "$SKOPIK_API_BASE/sessions" \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"agent\": \"mike\",
\"prompt\": \"Review the mounted folder and summarize anything stale.\",
\"remote\": \"$REMOTE_ID\"
}"
Store the returned registrationToken on the host — it is returned once, at registration, and never again. The desktop app, CLI, or Remote SDK authenticates with that token to heartbeat and poll for work. Bind any session to the remote by passing remote when you open it; omit it for cloud-only work, and open a new session to move work to a different remote.
Reference
remotes.register(input)
Register a customer-managed remote host.
const { remote, registrationToken } = await client.remotes.register({
hostKind: 'desktop',
label: 'Design workstation',
installId: 'design-ws-014',
tools: [
{ name: 'render_preview', readonly: true, needsApproval: false },
],
})
registration = client.remotes.register(
host_kind="desktop",
label="Design workstation",
install_id="design-ws-014",
tools=[{"name": "render_preview", "readonly": True, "needs_approval": False}],
)
curl "$SKOPIK_API_BASE/remotes/register" \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"host_kind": "desktop",
"label": "Design workstation",
"install_id": "design-ws-014",
"tools": [
{ "name": "render_preview", "readonly": true, "needs_approval": false }
]
}'
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
hostKind |
string | Yes | desktop, cli, or external. |
label |
string | Yes | Human-readable remote label. |
installId |
string | Yes | Stable install id from the remote process. |
enabled |
boolean | No | Whether this remote can claim serving windows. Defaults to true. |
maxConcurrentTasks |
number | No | Maximum serving windows claimed at once. Defaults to 1. |
readonly |
boolean | No | Whether the remote refuses write operations. Defaults to false. |
tools |
RemoteToolDescriptor[] | No | Custom tools the remote advertises. See RemoteToolDescriptor. |
version |
string | No | Remote version. |
Returns RegisterRemoteResponse — { remote: Remote, registrationToken: string }. The registrationToken is only returned from this call; store it on the host.
remotes.list(params?)
List remotes in the current workspace.
const { data: remotes } = await client.remotes.list()
remotes = client.remotes.list().data
curl "$SKOPIK_API_BASE/remotes" \
-H "Authorization: Bearer $SKOPIK_API_KEY"
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
limit |
number | No | Page size. |
cursor |
string | No | Cursor from the previous page. |
Returns ListResponse<Remote> — { data: Remote[], page? }. See Remote.
remotes.get(remoteId)
Read remote details.
const { remote } = await client.remotes.get(remoteId)
remote = client.remotes.get(remote_id).remote
curl "$SKOPIK_API_BASE/remotes/$REMOTE_ID" \
-H "Authorization: Bearer $SKOPIK_API_KEY"
Returns { remote: Remote } — see Remote.
remotes.update(remoteId, input)
Update remote policy, status, or capacity.
const { remote } = await client.remotes.update(remoteId, {
maxConcurrentTasks: 2,
status: 'paused',
})
remote = client.remotes.update(
remote_id,
max_concurrent_tasks=2,
status="paused",
).remote
curl "$SKOPIK_API_BASE/remotes/$REMOTE_ID" \
-X PATCH \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"max_concurrent_tasks": 2,
"status": "paused"
}'
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
label |
string | No | New label. |
enabled |
boolean | No | Whether this remote can claim serving windows. |
maxConcurrentTasks |
number | No | Maximum serving windows claimed at once. |
status |
string | No | offline, connecting, online, busy, paused, or error. |
Returns { remote: Remote } — see Remote.
remotes.heartbeat(remoteId, input?)
Report host liveness and refresh advertised capabilities. Hosts send heartbeats authenticated with the registration token; the desktop app, CLI, and Remote SDK do this for you on the returned nextHeartbeatInSeconds cadence.
const heartbeat = await client.remotes.heartbeat(remote.remoteId, {
status: 'online',
version: '1.4.2',
})
console.log(heartbeat.nextHeartbeatInSeconds)
heartbeat = client.remotes.heartbeat(remote_id, status="online", version="1.4.2")
print(heartbeat.next_heartbeat_in_seconds)
curl "$SKOPIK_API_BASE/remotes/$REMOTE_ID/heartbeat" \
-H "Authorization: Bearer $SKOPIK_REGISTRATION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "online",
"version": "1.4.2"
}'
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
status |
string | No | offline, connecting, online, busy, paused, or error. |
version |
string | No | Remote version. |
readonly |
boolean | No | Whether the remote refuses write operations. |
tools |
RemoteToolDescriptor[] | No | Replacement tool list. See RemoteToolDescriptor. |
Returns RemoteHeartbeat.
remotes.poll(options?)
Long-poll for the next assignment — a serving window for a remote-bound session, or a workspace cleanup. The remote is identified by the registration token, so there is no remote id in the path: construct the client with the registration token as its token. Resolves null when the wait window closes with no work (HTTP 204); poll again in a loop.
const host = createSkopikClient({ token: registrationToken })
const assignment = await host.remotes.poll({ wait: 25 })
if (assignment?.kind === 'session.serve') {
// prepare sessions/{sessionId}/work and serve operations for this window
}
host = Skopik(token=registration_token)
assignment = host.remotes.poll(wait=25)
if assignment and assignment.kind == "session.serve":
... # prepare sessions/{session_id}/work and serve operations for this window
curl "$SKOPIK_API_BASE/remotes/poll?wait=25" \
-H "Authorization: Bearer $SKOPIK_REGISTRATION_TOKEN"
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
wait |
number | No | Seconds to hold the poll open before the server returns empty. |
Returns RemoteAssignment | null — null when no work arrives within the wait window. See RemoteAssignment.
Types
Remote
| Field | Type | Notes |
|---|---|---|
remoteId |
string | Remote id. |
orgId |
string | Workspace id. |
hostKind |
string | desktop, cli, or external. |
label |
string | Remote label. |
installId |
string | Stable install id. |
enabled |
boolean | Whether the remote can claim serving windows. |
maxConcurrentTasks |
number | Maximum serving windows claimed at once. |
activeTaskCount |
number | Serving windows currently claimed. |
readonly |
boolean | Whether the remote refuses write operations. |
tools |
RemoteToolDescriptor[] | Custom tools the remote advertises. See RemoteToolDescriptor. |
version |
string or null | Remote version. |
status |
string | offline, connecting, online, busy, paused, or error. |
lastHeartbeatAt |
string or null | ISO timestamp. |
lastPollAt |
string or null | ISO timestamp. |
createdAt |
string | ISO timestamp. |
updatedAt |
string | ISO timestamp. |
RegisterRemoteResponse
| Field | Type | Notes |
|---|---|---|
remote |
Remote | Registered remote. See Remote. |
registrationToken |
string | Host token. Returned only at registration — store it on the remote. |
RemoteToolDescriptor
| Field | Type | Notes |
|---|---|---|
name |
string | Tool name. |
description |
string or null | Tool description. |
readonly |
boolean | Whether the tool only reads. |
needsApproval |
boolean | Whether calls require human approval. |
inputSchema |
object or null | JSON Schema for tool input. |
RemoteHeartbeat
| Field | Type | Notes |
|---|---|---|
remoteState |
string | Server-side remote status. |
nextHeartbeatInSeconds |
number | When to send the next heartbeat. |
serverTime |
string | ISO timestamp. |
remote |
Remote | Updated remote when included. See Remote. |
RemoteAssignment
A discriminated union on kind: session.serve (a claimed serving window) or workspace.cleanup (remove a canceled session's local folder).
session.serve
| Field | Type | Notes |
|---|---|---|
kind |
string | session.serve. |
assignmentId |
string | Assignment id. |
remoteId |
string | Remote id. |
orgId |
string | Workspace id when included. |
session |
object | { runId, sessionId, agentId, agentLabel?, userId? } for the window. |
lease |
object | { leaseId, expiresAt } — the claim on this window. |
runtimeToken |
string | Token for run-scoped runtime calls during the window. |
runtimeTokenExpiresIn |
number | Runtime token lifetime in seconds. |
nextHeartbeatInSeconds |
number | When to send the next heartbeat. |
workspace.cleanup
| Field | Type | Notes |
|---|---|---|
kind |
string | workspace.cleanup. |
assignmentId |
string | Assignment id. |
remoteId |
string | Remote id. |
orgId |
string | Workspace id when included. |
sessionId |
string | Session whose local folder ({root}/sessions/{sessionId}/) should be deleted. |
reason |
string or null | Cleanup reason (e.g. session_canceled). |
nextHeartbeatInSeconds |
number | When to send the next heartbeat, when included. |