Skip to main content
Spawns a command on your machine against Archal’s cloud-hosted twins. No OpenClaw, no remote endpoint. Just your headless agent harness.

Minimal setup

Point --harness at the headless harness entrypoint. For an Exo-style app, that is usually a thin file like .archal/harness.ts that imports the real agent runtime while Archal intercepts the service traffic underneath:
archal run \
  --task "Read the unread inbox email and create a draft reply. Do not send the email." \
  --twin google-workspace \
  --harness ./.archal/harness.ts \
  --no-docker
Archal runs the local agent command from one of these sources:
  • an explicit harness entrypoint such as .archal/harness.ts
  • package.json#archal.harness
  • archal-harness.json
  • a narrow legacy fallback from package.json scripts archal:harness or archal, or root agent.mjs/js/ts
If the harness directory has one of those narrow safe fallbacks, Archal can auto-generate the minimal manifest for you. It no longer guesses from generic eval scripts. Want a concrete fixture to adapt? Start from:
archal run examples/agents/google-workspace-local-tools/scenario.md \
  --harness examples/agents/google-workspace-local-tools \
  --no-docker \
  -n 1
That example keeps the tool layer local and shows the Gmail/Calendar integration shape. It is the reference fixture for hosted local-harness runs against the Google Workspace twin. If you are writing your own Gmail/Calendar smoke test, copy this shape:
# Read and Draft Reply

## Task

Read the unread inbox email and create a draft reply. Do not send the email.

## Checks

- [D] The run exits successfully

## Config

twins: google-workspace
Google Workspace defaults to assistant-baseline, so you can omit both Setup and seed: for this first smoke test.

First command to run

Use archal harness check before the first scenario run:
archal harness check ./.archal/harness.ts
That check is the intended onboarding path for third-party repos. It resolves the harness, shows the runnable entrypoint Archal found, and runs the same headless boot preflight that archal run uses later.

What “headless” means

Headless does not mean “no product UI exists.” It means Archal can run the agent without starting the app shell. Good fit:
  • a Node command that imports the real agent runtime
  • the same tools and provider wiring the app normally uses
  • service calls going through clients or proxies that can be redirected
Poor fit:
  • booting Electron just to reach the agent
  • renderer-only entrypoints
  • UI click simulation as the main integration path
If your app is not headless today, the normal fix is a thin .archal/harness.ts adapter that imports the same runtime and skips the shell.

Where traces go

During a run, Archal gives the harness temp file paths for metrics and agent trace collection. The harness can write to those paths, and Archal ingests them into the run result before cleaning up the temp files. If you want a coding agent to inspect the full run result afterward, use JSON output and save it:
archal run \
  --task "Read the unread inbox email and create a draft reply. Do not send the email." \
  --twin google-workspace \
  --harness ./.archal/harness.ts \
  --no-docker \
  -o json > archal-run.json
For hosted runs, Archal also stores managed traces in the product, so you can inspect them later with the trace commands and the dashboard.

Adding a manifest

For more control, drop an archal-harness.json at the resolved harness root. For the .archal/harness.ts convention, that means the repo root. This is optional but lets you configure the command, model, prompt files, and environment variables.

Minimal .archal/harness.ts

This file is just a headless entrypoint. Archal runs it for you.
const task = process.env.ARCHAL_ENGINE_TASK?.trim();

if (!task) {
  throw new Error("ARCHAL_ENGINE_TASK is required");
}

const result = await runMyAgent(task);

if (typeof result === "string") {
  console.log(result);
} else {
  console.log(JSON.stringify(result));
}
In a real repo, runMyAgent() should call the same model runtime and tool wiring your app already uses. The harness file is just the thin adapter that turns that runtime into a headless command.

What the harness needs to do

The required contract is small:
  • read ARCHAL_ENGINE_TASK
  • call the real model runtime
  • use service clients or proxies that can be redirected under Archal
  • print the final result to stdout
The “model runtime” is the code path that actually instantiates the provider, registers tools, and runs the agent loop. In most repos, the harness should just import that code and call it. If the runtime talks to Gmail, Calendar, Slack, Jira, or other services through app-specific clients, those clients are the bridge point. They must be configurable enough to talk to Archal twins instead of assuming the full app environment.

First run

archal run automatically boot-checks a local harness before it provisions hosted twins. If that preflight fails, Archal now classifies the error into the usual buckets:
  • headless boot issues such as Electron-only imports or Vite-only globals on the harness path
  • native dependency issues such as Node vs Electron ABI mismatches
  • service bridge issues such as unwired dbProxy or gmailProxy boundaries
  • service config issues such as missing OAuth credentials or API keys
If you want to reproduce that failure outside Archal after archal harness check fails, run the harness directly:
ARCHAL_ENGINE_TASK="Reply with exactly OK and do not use tools." \
  npx tsx .archal/harness.ts
Then run the scenario:
archal run scenario.md \
  --harness ./.archal/harness.ts \
  --no-docker
If the harness fails there too, the next step is to fix the harness boundary or service bridge, not the scenario. See Harness configuration for the full manifest reference.

Set the directory once

export ARCHAL_DEFAULT_HARNESS=./.archal/harness.ts
archal run scenario.md -n 3
Archal picks harness mode automatically when ARCHAL_DEFAULT_HARNESS is set and no --engine-endpoint is provided.

Override the model

archal run scenario.md \
  --harness ./.archal/harness.ts \
  --agent-model "claude-sonnet-4-20250514"