Skip to main content

The agent has a body

When the agent needs to do real work, it does not run a server-side stub. It runs on a dedicated Fly Machine provisioned as that agent’s body: a multi-core performance box with Python 3.12, Node 22, git, and bash. The agent execs real commands on it through Fly’s Machines API, the machine is metered, and it suspends to $0 compute when idle. This is the “run real operations at any scale” substrate, reused from the clawd.run launchpad and refocused on autonomous agent bodies. The implementation is src/lib/hermesco/fly.ts.

Default spec

AGENT_SPEC = {
  image: "nikolaik/python-nodejs:python3.12-nodejs22",
  cpuKind: "performance",   // dedicated vCPUs, not the shared-cpu minimum
  cpus: 2,
  memoryMb: 4096,
  region: "iad",
  app: "hermesco-agents",
}
Every value is tunable via environment variables. See Configuration.

Lifecycle

provisionAgentMachine()  ->  create + wait for "started"
        |
        v
   execOnMachine(id, cmd)  ->  real exit code + stdout/stderr
        |
        v
   suspendAgentMachine(id) ->  state "suspended", compute cost stops accruing
        |
        v
   startAgentMachine(id)   ->  warm resume
        |
        v
   destroyAgentMachine(id) ->  cleanup
Agent bodies live in a dedicated Fly app (hermesco-agents) that is separate from the web app. They do not need a public IP because all execution happens through the Machines API.

Reuse by workspace

An agent body is tied to a workspace. ensureAgentBody(workspaceId, goal) finds the existing, non-destroyed machine for that workspace or provisions a new one. This means a workspace keeps the same machine (and its files) across directives, rather than spinning up a fresh box every time.
const machine = await ensureAgentBody(workspaceId, goal);
const run = await execOnMachine(machine.id, command);

Compute cost is telemetry, not a silent debit

The fleet surfaces an estimated Fly compute cost per machine. It accrues only while a machine’s state is started:
// mapMachine, src/lib/hermesco/fly.ts
const billing = state === "started";
const uptimeMs = billing ? Date.now() - createdAt : 0;
const computeCostUsd = billing ? (uptimeMs / 60000) * COMPUTE_USD_PER_MIN : 0;
This is a labelled estimate shown as infrastructure telemetry. It is never silently debited from the Treasury, which stays driven by real Stripe activity and human-approved spends.

Honest fallback

If FLY_API_TOKEN is not set, flyConfigured() returns false. The fleet endpoint reports configured: false and run_in_sandbox falls back to a Daytona sandbox. Nothing is faked; the system simply uses the substrate that is available.
// src/lib/hermesco/sandbox.ts
export async function runForAgent(workspaceId, command, opts) {
  if (flyConfigured()) {
    const machine = await ensureAgentBody(workspaceId, opts?.goal);
    return execOnMachine(machine.id, command, opts);  // substrate: "fly-machine"
  }
  return runInSandbox(command, opts);                   // substrate: "daytona"
}