> ## Documentation Index
> Fetch the complete documentation index at: https://docs.archal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first harness

> Write the small TypeScript entrypoint Archal runs instead of your app UI: read AGENT_TASK, call your real agent, and print the final answer.

A harness is the small program Archal runs instead of your app UI. It reads one
task, calls your real agent code, and prints the final answer.

`npx archal init` generates one for you at `./.archal/harness.mjs` (run with
`node`, no extra tooling). Prefer TypeScript? Put it at `./.archal/harness.ts`
and run it with `tsx` instead - the runner accepts `harness.{mjs,js,ts,cjs}`.

## Template

```ts ./.archal/harness.ts theme={null}
const task = process.env.AGENT_TASK;
if (!task) {
  console.error('Missing AGENT_TASK');
  process.exit(1);
}

// Call your real agent runtime. Do not boot the full app shell.
const result = await runMyAgent({ task });

const text = typeof result === 'string' ? result : JSON.stringify(result);
console.log(JSON.stringify({ text }));
```

Send logs to stderr. Keep stdout for the final answer.

## What Archal reads from stdout

Archal extracts the final answer from stdout in this order:

1. **One JSON object.** Archal reads `payloads[]` or `text`. Recommended:
   ```json theme={null}
   { "text": "The final answer." }
   ```
2. **JSON object in mixed stdout.** Works, but logs can make this brittle.
3. **NDJSON.** Useful if your harness streams events.
4. **Plain text.** Archal joins non-empty stdout lines outside JSON blocks.

For most harnesses, print exactly one JSON object.

## Environment variables

Archal sets these on the harness process before it spawns:

| Variable              | Description                                                                             |
| --------------------- | --------------------------------------------------------------------------------------- |
| `AGENT_TASK`          | The full task text (scenario prompt or `--task "..."`).                                 |
| `AGENT_CLONE_URLS`    | JSON map of clone names to REST base URLs for local harnesses.                          |
| `AGENT_ROUTE_HEADERS` | JSON headers to include when a local harness calls an `AGENT_CLONE_URLS` endpoint.      |
| `NODE_EXTRA_CA_CERTS` | Path to the short-lived CA cert when the runtime cannot rely on the system trust store. |

Archal does not inject your model keys. Put `OPENAI_API_KEY`,
`ANTHROPIC_API_KEY`, and similar secrets in `.env` or CI secrets.

## Exit-code contract

| Code     | Meaning                                                                    |
| -------- | -------------------------------------------------------------------------- |
| `0`      | Run succeeded, final answer printed to stdout. Evaluator scores the trace. |
| non-zero | Runtime error. The run is marked failed; `[P]` criteria are not evaluated. |

## How to route service traffic

Use the same SDKs, service domains, and normal credential env vars your agent
would use in production. For example, use Octokit against `api.github.com`.

Transparently routing **unmodified** SDKs that call real service domains (like
Octokit against `api.github.com`) requires Docker or sandbox mode, so Archal can
control DNS and TLS trust. A harness that instead calls the clone URL from
`AGENT_CLONE_URLS` directly is scored without either (see the minimal example
below).

For a local harness that calls the clone directly instead of using Docker or
sandbox routing, point the SDK at the matching URL in `AGENT_CLONE_URLS` and merge
`AGENT_ROUTE_HEADERS` into each clone request. Keep the normal service
authorization header too, for example `Authorization: Bearer <test-token>` for
GitHub REST.

## Minimal clone-calling harness (no agent framework)

If you do not have an agent yet, the fastest path to a first score is a harness
that calls the clone's REST API directly. Archal injects `AGENT_CLONE_URLS` and
`AGENT_ROUTE_HEADERS` when it starts the run, so the harness needs no Archal
SDK. This one matches the `scenarios/first-run.md` that `archal init` scaffolds

* list and summarize GitHub issues, read-only:

```js ./.archal/harness.mjs theme={null}
// Archal injects AGENT_CLONE_URLS + AGENT_ROUTE_HEADERS when it starts the run.
const task = process.env.AGENT_TASK ?? '';
const cloneUrls = JSON.parse(process.env.AGENT_CLONE_URLS ?? '{}');
const routeHeaders = JSON.parse(process.env.AGENT_ROUTE_HEADERS ?? '{}');

const base = cloneUrls.github; // key matches "clones": ["github"] in .archal.json
if (!base) {
  // Expected when you run this file directly - clone URLs only exist during `archal run`.
  console.log(JSON.stringify({ text: 'No GitHub clone URL was provided.' }));
  process.exit(0);
}

const headers = {
  ...routeHeaders, // x-route-authorization: authenticates YOU to Archal
  // Default GitHub clone bootstrap token (per-service list: /guides/authentication):
  Authorization: 'Bearer ghp_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTt',
  Accept: 'application/vnd.github+json',
};

// Discover the seeded repos, then list each repo's issues.
const repos = await (await fetch(`${base}/user/repos`, { headers })).json();
const lines = [];
for (const repo of repos) {
  const res = await fetch(`${base}/repos/${repo.full_name}/issues`, { headers });
  for (const issue of await res.json()) {
    lines.push(`#${issue.number} ${issue.title} (${repo.full_name})`);
  }
}

console.log(JSON.stringify({
  text: `Task: ${task}\nOpen issues across ${repos.length} repo(s):\n${lines.join('\n')}`,
}));
```

Those two headers do different jobs: `AGENT_ROUTE_HEADERS`
(`x-route-authorization`) authenticates **you to Archal**, while the normal
service `Authorization` header is what the **clone** validates - keep both. The
default bootstrap token for each service is listed in
[Authentication](/guides/authentication). Once you have a real agent, swap this
for a call into it and let it use normal SDKs.

## Check it locally

Run the entrypoint directly without provisioning clones:

```bash theme={null}
# .mjs (what `archal init` scaffolds), run with node:
AGENT_TASK="Reply with OK and do not use tools." node ./.archal/harness.mjs
# .ts variant, run with tsx:
AGENT_TASK="Reply with OK and do not use tools." npx tsx ./.archal/harness.ts
```

That should print a final answer and exit `0`. (The clone-calling example above
prints `No GitHub clone URL was provided.` here - that is expected; the clone
URLs only exist during `archal run`.) If it hangs or crashes, `archal run` will
too.
