Skip to content

Connected Compute

Route governed work to customer-owned machines or LumenFlow-managed runners with one policy, lease, evidence, routing, and metering model.

BYOC and managed runners under one governance model#

Connected Compute is the workspace primitive for running governed agent jobs on either BYOC sources you operate or LumenFlow-managed sources. A source can be a laptop, CI runner, Docker host, Kubernetes worker, or the first managed pool.

Both paths use the same control-plane contract:

LayerWhat is shared
ClaimPOST /api/v1/runtime/claim assigns eligible runtime work
LeaseLease records bound execution and retry state
EvidenceReceipts, approvals, and replay links stay workspace-scoped
RoutingWorkspace policy chooses byoc_only, byoc_first, managed_first, or managed_only
BudgetPer-job limits refuse before silent fallback

When you post a job, the capabilities you require decide where it can run. A short list of capabilities — including local tool execution, browser actions, local model generation, energy sampling, and MCP tool access — run only on a runner you connect; if no approved runner is online, the job stays queued instead of running somewhere that cannot actually do the work. Jobs that only need LumenFlow-managed capabilities run on the hosted service without any runner connected. Approving a capability for a runner on the compute page covers it regardless of which equivalent name that runner later reports it under — you approve once, not once per runner version. If a runner asks to claim work with a capability you have not yet approved, it shows up on the compute page for you to review and approve, without needing to reconnect the runner.

What is live#

  • connected-runtime enrollment and health posture
  • the runtime claim path and assignment lifecycle
  • compute source read models for operator dashboards
  • workspace routing policy UI and per-job "assigned to X, why" detail
  • the first managed-worker registration path
  • a runner-side compute-class classification (the default is cpu-small), recorded on each assignment run

Current runner setup#

Open /dashboard/{workspaceId}/compute and use Runner setup. Enrolling a runner creates a one-time runtime credential and walks through install, enrol, doctor, approve, and start steps for your platform.

On Linux, the verified path is the pnpm CLI already in your LumenFlow checkout: pnpm compute:connect, compute:doctor, compute:run and compute:service. It needs one prerequisite package — bubblewrap, which confines local tool execution — and the same runner directory flows through every step. The start step renders a systemd --user unit with Restart=always. Once doctor confirms the sandbox is available, the runner requests the local:tool_exec and agent:code capabilities, which you approve from the same page before the service starts.

compute:service installs the runner as a systemd service. install runs in the systemd user scope by default, and needs --operator-approved once the runner's capabilities and posture are approved in the workspace. status, drain and uninstall manage the installed service. Host actuation is Linux only. On macOS and Windows, compute:service install --dry-run prints the plan and the other verbs refuse. Windows and macOS use the diagnostic bootstrap below.

Local tool execution only ever runs inside a checkout under ~/.lumenflow/workspaces — the start step creates one there for you (a detached git worktree) and points --workspace-root at it. Any other directory is refused, so doctor reporting a workspace-root problem means the fix is a checkout in that directory, not somewhere else on the host.

The diagnostic bootstrap commands use Node to fetch the app-hosted bootstrap from /api/v1/runner/bootstrap. They do not install @hellmai/lumenflow-cli, do not require npm registry access, and do not require LUMENFLOW_CONTROL_PLANE_TOKEN for doctor or run. The runtime claim made with the one-time credential registers the runner session on the cloud side.

Treat the runtime credential as burned if it appears in chat, logs, shell history you do not control, screenshots, or any shared channel. Re-enrol the runner and use the newly shown credential.

Runner onboarding for agents#

An agent can bring a runner online under a policy the workspace owner sets. The pieces are separate, and each one is checked on every use.

  • Policy. An owner or admin session sets the policy with PUT /api/v1/workspaces/{workspaceId}/runner-onboarding/policy. No key, grant or agent can change it. A change that widens the policy takes effect only after a second, distinct owner or admin confirms it with POST /api/v1/workspaces/{workspaceId}/runner-onboarding/policy/confirm. GET on the same path shows the policy in force, its expiry, and any widening that is waiting for a second approver. Once the policy expires, no grant can be issued and a held grant is refused.
  • Grant. An owner or admin session, or a key with runners:manage, issues a grant with POST /api/v1/workspaces/{workspaceId}/runner-onboarding/grants. The grant is an lf_rog_ credential that carries only runner:onboard.* scopes. Its lifetime is 7 to 30 days, 7 by default. A workspace holds a capped number of live grants. Revoke one with DELETE /api/v1/workspaces/{workspaceId}/runner-onboarding/grants/{grantId}. A revoked grant is refused on its next use.
  • Bootstrap. The runner's doctor and run steps fetch the runner script from GET /api/v1/runner/bootstrap, as described above.
  • Capability approval. The runner's requested capabilities are approved by an owner in the workspace, before the service starts.

The credential kinds and their scopes are listed in Credentials and scopes.

Claim and heartbeat pacing#

Every claim and heartbeat response tells your runner how soon to check in again. An ordinary response carries a poll-after hint in milliseconds — 30 seconds for claim, 60 seconds for heartbeat by default — so a well-behaved runner never needs a fixed guess. If your workspace's request budget is temporarily used up, the response is a 429 with a Retry-After header naming the minimum wait before trying again; if the platform's own admission check is briefly unavailable, you get a 503 with the same kind of Retry-After header rather than a silent failure. Both are temporary and independent of your billing or credit balance. The published runner client already honours these headers — you do not need to implement backoff yourself.

Job results#

Whichever substrate runs the work — a LumenFlow-managed worker or a connected runner you operate — the result comes back in one shape: the lumenflow.job_result/v1 envelope. Poll GET /api/v1/sidekick/jobs/{job_id} (or watch the SSE stream) until the top-level status field reaches succeeded or failed, then read the envelope from the response's result_envelope field (it stays null until it is stored).

needs_approval is a paused state, not a finished one — the job is waiting on a human to approve or reject it, not still running and not done. It is visible on result_envelope as soon as it is stored, the same way a finished job's envelope is, but it does not fire a push, channel, or webhook delivery the way succeeded/failed do; keep polling (or watching the SSE stream) past it for the job's eventual succeeded or failed outcome.

result_envelope fieldMeaning
statussucceeded, failed, needs_approval, or cancelled (an owner stop)
summaryOne paragraph a human can read
outputsWhatever structured data your job produced
artifact_refsLinks to any files or artifacts the job created
evidence_refsLinks into the workspace's evidence trail for this job
correlationEchoed back exactly as you sent it, unmodified
errornull on success; a reason code and message on failure

The envelope is identical regardless of where the job actually ran, so your integration never needs to know or care which substrate executed it.

When LumenFlow's own hosted worker runs the job (rather than a runner you operate), a failed job's error.reason_code is one of a small, stable set, so your integration can react without parsing the summary text:

reason_codeMeaning
model_unavailableThe workspace has no usable AI model configured for this kind of job
model_provider_errorThe configured model's own API call failed — the message names the model and the HTTP status, never the raw response from the model provider
managed_artifact_persistence_errorThe job produced its output, but saving it failed
runtime_worker_errorAny other hosted-worker failure

A runner you operate is free to send its own reason_code values in result.error — these four only describe what the hosted worker sets.

Rather than polling, a Job created with callback_url (and a callback_secret to sign it) gets this same envelope pushed to you once it reaches a terminal state — see "Receiving results by webhook" in the project-connection guide for the exact headers and a signature-verification snippet.

If the runner executing your job goes dark mid-run — a crash, a dropped connection, a powered-off machine — and never reports back, the platform eventually gives up retrying and delivers a failed result with reason lease_expired, so your integration never waits forever for a runner that is not coming back.

Governed execution is billed on BYOC too#

Running work through Connected Compute on a BYOC source you operate is billed for the governance plane that ran it: admission, lease and fencing, sandboxed execution, evidence, and approvals — every completed run is recorded as usage. Your own infrastructure cost stays yours; Free and Team include a monthly allowance of governed-execution seconds for work on your own hardware (not AI usage), with metered overage beyond it on Team. LumenFlow-managed compute is billed separately, per the existing managed-compute credit model.

Private-network boundary#

Connected Compute is not a raw tunnel. Private-network access composes with the federation runtime and its admission gates. A BYOC source can execute approved work, but it does not silently become a private-network proxy.