Docs

Remote Commands

Invoke explicit handlers on remote deployments without opening inbound ports.

Your software runs in the customer's cloud. Commands are how your backend talks to it — invoke explicit handlers on remote deployments without opening inbound ports.

This is the primary way your cloud service communicates with customer deployments. An AI agent sends tool calls to a worker in the customer's VPC. A data connector runs queries against a private database. A dashboard pulls live status. Each of these is a command: your code defines handlers, your backend invokes them, and responses come back. No open ports, no VPN, no generic shell.

Define a Handler

import { command } from "@alienplatform/sdk"

command("generate-report", async ({ startDate, endDate }) => {
  const data = await fetchData(startDate, endDate)
  return { report: aggregate(data), rowCount: data.length }
})

command("run-migration", async ({ version }) => {
  await migrate(version)
  return { status: "completed" }
})

For bounded request-response command handlers, enable commands on a Worker:

const toolExecutor = new alien.Worker("tool-executor")
  .code({ type: "source", src: "./", toolchain: { type: "typescript" } })
  .commandsEnabled(true)
  .build()

For command handlers that should keep running, use a Daemon. Daemon is supported on Local and Kubernetes today; AWS, GCP, and Azure daemon support is not available yet.

const connector = new alien.Daemon("connector")
  .code({ type: "source", src: "./connector", toolchain: { type: "typescript" } })
  .commandsEnabled(true)
  .build()

Invoke from the CLI

alien commands invoke \
  --deployment acme-corp \
  --command generate-report \
  --params '{"startDate": "2025-01-01"}'

Invoke from Code

import { CommandsClient } from "@alienplatform/sdk/commands"

const commands = new CommandsClient({
  managerUrl: "https://manager.example.com",
  deploymentId: "deployment_123",
  token: "your_api_key",
})

const result = await commands.invoke("generate-report", {
  startDate: "2025-01-01",
  endDate: "2025-03-31",
})

How It Works

  1. You invoke a command (via CLI or SDK).
  2. Alien stores the request.
  3. The command is dispatched to the customer's environment:
    • Push model: direct invocation — Lambda invoke, Pub/Sub message, Service Bus message.
    • Pull model: the Operator picks it up on the next sync cycle.
  4. The handler runs and produces a response.
  5. You read the result.

The customer's environment never needs to open a port or allow inbound traffic. Push deployments dispatch commands through scoped cloud-provider APIs; pull deployments pick them up over outbound HTTPS.

Real-World Examples

AI Agent — remote tool calls

import { command, storage } from "@alienplatform/sdk"

command("read-file", async ({ path }) => {
  const ws = await storage("workspace")
  return ws.get(path)
})

command("write-file", async ({ path, content }) => {
  const ws = await storage("workspace")
  return ws.put(path, content)
})

command("list-files", async ({ directory }) => {
  const safePath = path.resolve("/workspace", directory)
  if (!safePath.startsWith("/workspace")) throw new Error("Invalid path")
  return fs.readdir(safePath)
})

The agent harness runs in your cloud — planning, model calls, orchestration. When it needs to act, it sends commands to the Worker or Daemon in the customer's environment. The handler controls what leaves as the command response: a file, an aggregate, a diff, or a redacted excerpt.

Data Connector — query behind the firewall

import { command, vault } from "@alienplatform/sdk"
import { Pool } from "pg"

command("get-users", async ({ status, limit }) => {
  const creds = await vault("credentials")
  const config = JSON.parse(await creds.get("warehouse"))
  const pool = new Pool(config)
  const { rows } = await pool.query(
    "SELECT id, name, email FROM users WHERE status = $1 LIMIT $2",
    [status, limit ?? 100]
  )

  return { rows, count: rows.length }
})

Warehouse credentials stay in the customer's vault. The query result is the command response, so the handler should return only the rows, aggregates, or diagnostics you intend to expose.

On this page