archal
v0.9.19
Published
Test your agents & integrations against service clones
Downloads
1,012
Readme
archal
Pre-deployment testing for AI agents via Archal's hosted service clones, including route-mode clones for Discord, GitHub, Slack, Stripe, Linear, Jira, Ramp, Apify, Tavily, Supabase, and Google Workspace.
Known compatibility notes
npm install -g archalmay print peer-dependency warnings from the vendored CLI runtime. These are safe to ignore; all required modules are bundled.- The
vitestintegration underarchal/vitestrequiresvitest@^2.1.0. Projects on vitest 3 should pin a workspace to vitest 2 for Archal tests to avoid duplicate module resolution.
File issues at https://github.com/Archal-Labs/archal/issues.
Install
npm install -g archalThen authenticate:
archal loginThis writes credentials to ~/.archal/credentials.json. You can also set
ARCHAL_TOKEN directly in CI. Use a workspace API key (archal_ws_...) for
CI instead of a personal token.
Workspace API keys are runtime and CI credentials bound to one workspace. They
can run clones, upload and read traces, and read usage for that workspace. They
cannot manage audit events or workspace API keys. Use an owner/admin user
credential, either archal login or a dashboard-issued user API key, for
workspace administration.
CLI
The CLI is the primary interface. archal run executes a
scenario (a markdown file describing a task, the expected behavior, and
success criteria) against a hosted clone session and reports a satisfaction
score.
Quick start
# 1. Log in
archal login
# 2. Initialize your project
archal init
# 3. Edit .archal/harness.mjs to call your agent
# 4. Run the starter scenario from .archal.json
archal runarchal init creates .archal.json, .archal/harness.mjs, and
scenarios/first-run.md. The generated harness is a guarded stub: Archal
refuses to run it until you edit the file to call your real agent, so a first
run cannot be scored from placeholder text.
archal run auto-discovers an .archal.json at the project root. A minimal
config looks like this:
{
"agent": {
"command": "node",
"args": [".archal/harness.mjs"]
},
"scenarios": ["scenarios/first-run.md"],
"clones": ["github", "stripe"],
"runs": 1
}Supported clones
archal clone lists the eleven clones available today:
| Clone | Notes | |---|---| | Discord | Guilds, channels, messages, members | | GitHub | Repos, issues, PRs, labels, reviews | | Slack | Channels, messages, users, reactions | | Stripe | Customers, subscriptions, invoices, products | | Linear | Teams, issues, projects, cycles | | Jira | Projects, issues, workflows, components | | Ramp | Cards, transactions, reimbursements, users | | Apify | Actors, tasks, runs, datasets | | Tavily | Search and extraction responses | | Supabase | Auth, Postgres, storage, edge functions | | Google Workspace | Gmail, Drive, Calendar, Docs, People |
Command reference
| Command | What it does |
|---|---|
| archal login | Authenticate with Archal |
| archal logout | Remove stored credentials |
| archal clone | Browse the clone catalog |
| archal clone start [clones...] | Start a hosted clone session |
| archal clone status | Inspect the active session |
| archal clone stop | Stop the active session |
| archal clone list | List all your active sessions |
| archal clone attach <uuid> | Reattach to a session by id |
| archal clone renew <seconds> | Extend the session lifetime |
| archal clone reset | Reset clone state without tearing down the session |
| archal clone seed <clone> <name> | Load a named seed into a running clone |
| archal run [scenario] | Run a scenario file (or use --config for .archal.json) |
| archal scenario list | Browse local and hosted scenarios |
| archal seed list [clone] | List prebuilt clone seeds |
| archal trace | View recent scenario traces |
| archal trace <name> | View trace details for a run |
| archal usage | Check active workspace session-minutes and plan |
Run archal <command> --help for flag details.
Vitest integration (secondary use case)
You can also import archal/vitest to route SDK traffic from a vitest
suite through a hosted clone, with no code changes to your production
code. This is useful if you want to test the HTTP side of an integration
without hitting real provider APIs.
The vitest helper supports the hosted route-mode clone catalog: Apify, Discord, GitHub, Google Workspace, Jira, Linear, Ramp, Slack, Stripe, Supabase, and Tavily. If you only need scenario-level evaluation, the CLI flow above is simpler to set up.
Minimal config
import { defineConfig } from 'vitest/config';
import { withArchal } from 'archal/vitest';
export default defineConfig({
test: withArchal(
{
// everything you already had in test:, unchanged
globals: true,
coverage: { provider: 'v8' },
},
{
services: {
stripe: { mode: 'route', seed: 'small-business' },
},
},
),
});withArchal(existingTest, { services }) wraps an existing vitest.config.ts's test: block, preserving every field you already had. Pass {} as the first argument if you're starting from scratch.
Your existing tests work unchanged:
import { it, expect } from 'vitest';
import Stripe from 'stripe';
it('creates a customer', async () => {
const stripe = new Stripe('sk_test_fake'); // fake key is fine
const customer = await stripe.customers.create({ // goes to clone, not real Stripe
email: '[email protected]',
});
expect(customer.id).toMatch(/^cus_/);
});Per-test state isolation
import { beforeEach } from 'vitest';
import { resetArchalClones } from 'archal/vitest';
beforeEach(async () => {
await resetArchalClones();
});Webhook testing
The hosted clone runs in AWS ECS, so it can't POST to your localhost.
Instead, your test pulls queued deliveries with waitForArchalWebhook()
and invokes your handler directly with the exact payload the clone would
have sent.
import { it, expect } from 'vitest';
import Stripe from 'stripe';
import { waitForArchalWebhook } from 'archal/vitest';
import { handleStripeWebhook } from './src/webhooks';
const stripe = new Stripe('sk_test_fake');
it('records a subscription when customer.subscription.created fires', async () => {
// 1. Register a webhook endpoint (the clone needs to know what events to queue)
await stripe.webhookEndpoints.create({
url: 'http://test.local/stripe-wh',
enabled_events: ['customer.subscription.created'],
});
// 2. Trigger the event
const customer = await stripe.customers.create({ email: '[email protected]' });
const sub = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_existing_in_seed' }],
});
// 3. Pull the queued delivery
const event = await waitForArchalWebhook('stripe', 'customer.subscription.created');
expect(event.payload.data.object.id).toBe(sub.id);
// 4. Invoke your handler with the exact payload
await handleStripeWebhook(event.body, event.headers['Stripe-Signature'], process.env.STRIPE_WEBHOOK_SECRET);
});Webhook coverage:
| Service | Support | Notes | |---|---|---| | Stripe | ✅ | | | GitHub | ✅ | | | Slack | ✅ | Receiver-side signature only | | Jira | ✅ | Delivered via history buffer (also POSTed to registered URLs) | | Linear | ✅ | Delivered via history buffer (also POSTed to registered URLs) | | Supabase | ❌ | Database-triggered; test against real Postgres | | Google Workspace | ❌ | GCP Pub/Sub push notifications, not webhooks |
Parallel workers caveat: waitForArchalWebhook() consumes deliveries
from a shared queue by default. When vitest runs test files in parallel
across workers, worker A can swallow worker B's event. If your tests
depend on webhook events, set testIsolation: 'serial' in your config.
Test isolation across parallel workers
Each vitest worker is routed to its own per-worker state on the clone,
so parallel tests across workers don't see each other's writes. The
integration reads VITEST_WORKER_ID in each worker process and tags
every outbound SDK request with an X-Archal-Worker-Id header. The clone
maintains a separate state engine per worker id, seeded from the
baseline on first request.
Isolation-enabled clones: Stripe, GitHub, Slack, Jira, Linear.
Clones without isolation (Supabase, Google Workspace, Ramp) fall back
to shared state. If your tests depend on global assertions against those
clones, set testIsolation: 'serial'.
Authentication
In priority order:
ARCHAL_TOKENenv var- Stored credentials from
archal loginin~/.archal/credentials.json
This auth is only for Archal's hosted session provisioning. Your app's
provider credentials do not need to be real when traffic is routed through
a clone. Use placeholder tokens that satisfy the SDK's local validation
rules, such as sk_test_fake, ghp_fake_token_for_clone, or a dummy
Google bearer token.
What you'll see in the terminal
On the first archal run, session provisioning takes about 30 seconds
(ECS Fargate cold start). A progress line prints every few seconds so
the wait is visible:
[archal] provisioning stripe clone... 5s
[archal] provisioning stripe clone... 10s
...Subsequent runs with the same configuration reuse the warm session (~2 seconds).
At the end of every run, the estimated workspace session-minute usage is printed:
[archal] ~2 clone-minutes for this run (38.5s × 1 clone: stripe)Docs
Full documentation: https://docs.archal.ai
