Skip to main content
Archal’s harness is just a subprocess — any language that can read env vars and print to stdout works. This page shows the Python shape. For the full contract see Your first harness.

Minimal harness

agent.py
import json
import os
import sys

# 1. Preflight short-circuit.
#    archal runs the harness with ARCHAL_PREFLIGHT=1 before it provisions
#    twins, just to confirm the entrypoint exists.
if os.environ.get("ARCHAL_PREFLIGHT") == "1":
    print("OK")
    sys.exit(0)

# 2. Read the task.
task = os.environ.get("ARCHAL_ENGINE_TASK")
if not task:
    print("Missing ARCHAL_ENGINE_TASK", file=sys.stderr)
    sys.exit(1)

# 3. Twin endpoints.
github_base = os.environ.get("ARCHAL_GITHUB_BASE_URL")

# 4. Call your real agent.
from my_package.agent import run_my_agent
result = run_my_agent(task=task, github_base=github_base)

# 5. Print the final answer to stdout. Send logs to stderr.
#    Archal prefers structured JSON — one object with a "text" or
#    "payloads" field is the simplest contract. See the TS harness
#    guide for the full extractor precedence order.
print(json.dumps({"text": result if isinstance(result, str) else json.dumps(result)}))

.archal.json

Point the agent field at whatever launcher your project uses:
{
  "agent": "python agent.py",
  "twins": ["github"]
}
Or with uv:
{
  "agent": "uv run agent.py",
  "twins": ["github"]
}
Or poetry / hatch / etc. — Archal just shells out to this command, so the exact launcher is up to you.

Calling twins

The same two options as TypeScript (see Your first harness):
import os
import requests

base = os.environ["ARCHAL_GITHUB_BASE_URL"]
token = os.environ["ARCHAL_TOKEN"]
resp = requests.get(
    f"{base}/repos/acme/webapp/issues",
    headers={"Authorization": f"Bearer {token}"},
)
SDK clients with configurable base URLs work too — for example stripe-python accepts stripe.api_base = os.environ["ARCHAL_STRIPE_BASE_URL"].

Sanity-check

ARCHAL_PREFLIGHT=1 ARCHAL_ENGINE_TASK="noop" python agent.py
# should print `OK` and exit 0