archal 0.9.19 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +62 -154
  2. package/agents/openclaw/.archal.json +8 -0
  3. package/agents/openclaw/Dockerfile +97 -0
  4. package/agents/openclaw/README.md +113 -0
  5. package/agents/openclaw/drive.mjs +301 -0
  6. package/agents/openclaw/extract-openclaw-response-text.mjs +49 -0
  7. package/agents/openclaw/package.json +9 -0
  8. package/agents/openclaw/scenarios/acceptance/github-escalation-and-cleanup.md +43 -0
  9. package/agents/openclaw/scenarios/acceptance/jira-linear-release-triage.md +39 -0
  10. package/agents/openclaw/scenarios/acceptance/multi-clone-customer-risk-review.md +45 -0
  11. package/agents/openclaw/scenarios/acceptance/slack-incident-handoff.md +42 -0
  12. package/agents/openclaw/scenarios/acceptance/stripe-dunning-remediation.md +40 -0
  13. package/agents/openclaw/scenarios/close-stale-issues.md +35 -0
  14. package/agents/openclaw/scenarios/github-issue-triage-read-only.md +44 -0
  15. package/agents/openclaw/workspace/AGENTS.md +23 -0
  16. package/agents/openclaw/workspace/IDENTITY.md +8 -0
  17. package/agents/openclaw/workspace/SOUL.md +14 -0
  18. package/agents/openclaw/workspace/TOOLS.md +54 -0
  19. package/clone-assets/apify/tools.json +256 -22
  20. package/clone-assets/calcom/tools.json +2062 -0
  21. package/clone-assets/clickup/tools.json +3245 -0
  22. package/clone-assets/customerio/tools.json +2350 -0
  23. package/clone-assets/datadog/tools.json +734 -0
  24. package/clone-assets/github/tools.json +307 -27
  25. package/clone-assets/gitlab/tools.json +10688 -0
  26. package/clone-assets/google-workspace/tools.json +18 -6
  27. package/clone-assets/hubspot/tools.json +1604 -0
  28. package/clone-assets/jira/fidelity.json +1 -1
  29. package/clone-assets/jira/tools.json +266 -543
  30. package/clone-assets/linear/tools.json +278 -40
  31. package/clone-assets/ownerrez/tools.json +548 -0
  32. package/clone-assets/pricelabs/tools.json +343 -0
  33. package/clone-assets/sentry/tools.json +2821 -0
  34. package/clone-assets/slack/tools.json +1 -2
  35. package/clone-assets/stripe/tools.json +299 -46
  36. package/clone-assets/supabase/tools.json +437 -0
  37. package/clone-assets/unipile/tools.json +408 -0
  38. package/clone-assets/webflow/tools.json +2185 -0
  39. package/dist/autoloop-worker-types-CHaclqtD.d.cts +160 -0
  40. package/dist/cli.cjs +127896 -84811
  41. package/dist/{vitest/chunk-L36NXAU6.js → commands/autoloop-hosted-control-plane-client.cjs} +16344 -18845
  42. package/dist/commands/autoloop-hosted-control-plane-client.d.cts +133 -0
  43. package/dist/commands/autoloop-pr-verification.cjs +4312 -0
  44. package/dist/commands/autoloop-pr-verification.d.cts +19 -0
  45. package/dist/commands/autoloop-result-parser.cjs +27058 -0
  46. package/dist/commands/autoloop-result-parser.d.cts +196 -0
  47. package/dist/commands/autoloop-worker.cjs +38488 -0
  48. package/dist/commands/autoloop-worker.d.cts +102 -0
  49. package/dist/eval-shim.mjs +216 -0
  50. package/dist/index.cjs +15 -2
  51. package/dist/index.d.cts +3 -1
  52. package/dist/layer-1-ziaLpfLr.d.cts +62 -0
  53. package/dist/metafile-cjs.json +1 -0
  54. package/dist/reporter-entry.js +3 -0
  55. package/dist/sdk/index.cjs +51630 -0
  56. package/dist/sdk/index.d.cts +626 -0
  57. package/dist/vitest/chunk-2PDHTPZC.js +4867 -0
  58. package/dist/vitest/chunk-7L4BBB6M.js +2613 -0
  59. package/dist/vitest/index.cjs +6152 -75718
  60. package/dist/vitest/index.d.ts +22 -86
  61. package/dist/vitest/index.js +45 -414
  62. package/dist/vitest/runtime/hosted-session-reaper.cjs +682 -34399
  63. package/dist/vitest/runtime/hosted-session-reaper.js +1 -1
  64. package/dist/vitest/runtime/setup-files.js +2 -2
  65. package/manifest.json +9 -0
  66. package/package.json +20 -14
  67. package/skills/archal-agent/SKILL.md +86 -0
  68. package/skills/autoloop/SKILL.md +391 -0
  69. package/skills/autoloop/references/hosted-sources.md +94 -0
  70. package/skills/autoloop/references/trace-schema-mapping.md +104 -0
  71. package/skills/eval/SKILL.md +17 -15
  72. package/skills/free-account/SKILL.md +103 -0
  73. package/skills/install-agent/SKILL.md +202 -0
  74. package/skills/onboard/SKILL.md +29 -18
  75. package/skills/scenario/SKILL.md +36 -18
  76. package/skills/seed/SKILL.md +238 -0
  77. package/skills/vitest/SKILL.md +3 -2
  78. package/dist/harness.cjs +0 -62
  79. package/dist/harness.d.cts +0 -20
  80. package/dist/seed/dynamic-generator.cjs +0 -45687
  81. package/dist/seed/dynamic-generator.d.cts +0 -106
  82. package/dist/vitest/chunk-WZ7SA4CK.js +0 -47369
  83. package/skills/attach/SKILL.md +0 -402
package/README.md CHANGED
@@ -9,9 +9,9 @@ Ramp, Apify, Tavily, Supabase, and Google Workspace.
9
9
  - `npm install -g archal` may print peer-dependency warnings from the
10
10
  vendored CLI runtime. These are safe to ignore; all required modules
11
11
  are bundled.
12
- - The `vitest` integration under `archal/vitest` requires `vitest@^2.1.0`.
13
- Projects on vitest 3 should pin a workspace to vitest 2 for Archal tests
14
- to avoid duplicate module resolution.
12
+ - The `vitest` integration under `archal/vitest` supports `vitest@>=2.1.0 <3`.
13
+ Projects on Vitest 3 should pin a workspace to Vitest 2 for Archal tests
14
+ until the integration supports Vitest 3's reporter/runtime APIs.
15
15
 
16
16
  File issues at <https://github.com/Archal-Labs/archal/issues>.
17
17
 
@@ -37,6 +37,41 @@ cannot manage audit events or workspace API keys. Use an owner/admin user
37
37
  credential, either `archal login` or a dashboard-issued user API key, for
38
38
  workspace administration.
39
39
 
40
+ ## SDK
41
+
42
+ The `archal` root import exposes the code-first SDK primitives: provision
43
+ clones, run your agent, collect an outcome, and judge it — all in one
44
+ package, no separate installs.
45
+
46
+ ```js
47
+ import { clones, criterion, judge, reportAgentMetrics, version } from 'archal';
48
+
49
+ const session = await clones(['github'], { seed: { github: 'small-project' } });
50
+ try {
51
+ // call your agent, pointing it at session.url('github')
52
+ const outcome = await session.outcome();
53
+ const judgment = await judge(outcome, [
54
+ criterion('The agent created an issue with the right title'),
55
+ ]);
56
+ console.log(judgment.score, judgment.passed);
57
+ } finally {
58
+ await session.stop();
59
+ }
60
+ ```
61
+
62
+ Available exports from `archal`:
63
+
64
+ | Export | Type | Description |
65
+ |---|---|---|
66
+ | `clones(services, opts?)` | function | Provision a hosted clone session |
67
+ | `criterion(text, opts?)` | function | Create an LLM-judged criterion |
68
+ | `judge(outcome, criteria, opts?)` | function | Grade an outcome against criteria |
69
+ | `reportAgentMetrics(metrics)` | function | Write token usage from a harness before exit |
70
+ | `version` | string | Package version |
71
+
72
+ The Vitest integration lives at `archal/vitest`. See
73
+ <https://docs.archal.ai/guides/vitest>.
74
+
40
75
  ## CLI
41
76
 
42
77
  The CLI is the primary interface. `archal run` executes a
@@ -55,8 +90,8 @@ archal init
55
90
 
56
91
  # 3. Edit .archal/harness.mjs to call your agent
57
92
 
58
- # 4. Run the starter scenario from .archal.json
59
- archal run
93
+ # 4. Run the starter scenario from .archal.json with controlled routing
94
+ archal run --docker
60
95
  ```
61
96
 
62
97
  `archal init` creates `.archal.json`, `.archal/harness.mjs`, and
@@ -71,7 +106,10 @@ config looks like this:
71
106
  {
72
107
  "agent": {
73
108
  "command": "node",
74
- "args": [".archal/harness.mjs"]
109
+ "args": [".archal/harness.mjs"],
110
+ "env": {
111
+ "OPENAI_API_KEY": "${OPENAI_API_KEY}"
112
+ }
75
113
  },
76
114
  "scenarios": ["scenarios/first-run.md"],
77
115
  "clones": ["github", "stripe"],
@@ -79,23 +117,12 @@ config looks like this:
79
117
  }
80
118
  ```
81
119
 
82
- ### Supported clones
120
+ The harness only receives variables declared in `agent.env`; use `${VAR}`
121
+ interpolation for shell secrets.
83
122
 
84
- `archal clone` lists the eleven clones available today:
123
+ ### Supported clones
85
124
 
86
- | Clone | Notes |
87
- |---|---|
88
- | Discord | Guilds, channels, messages, members |
89
- | GitHub | Repos, issues, PRs, labels, reviews |
90
- | Slack | Channels, messages, users, reactions |
91
- | Stripe | Customers, subscriptions, invoices, products |
92
- | Linear | Teams, issues, projects, cycles |
93
- | Jira | Projects, issues, workflows, components |
94
- | Ramp | Cards, transactions, reimbursements, users |
95
- | Apify | Actors, tasks, runs, datasets |
96
- | Tavily | Search and extraction responses |
97
- | Supabase | Auth, Postgres, storage, edge functions |
98
- | Google Workspace | Gmail, Drive, Calendar, Docs, People |
125
+ Run `archal clone` for the current hosted catalog, and `archal clone list` for active clone sessions.
99
126
 
100
127
  ### Command reference
101
128
 
@@ -108,151 +135,32 @@ config looks like this:
108
135
  | `archal clone status` | Inspect the active session |
109
136
  | `archal clone stop` | Stop the active session |
110
137
  | `archal clone list` | List all your active sessions |
111
- | `archal clone attach <uuid>` | Reattach to a session by id |
112
138
  | `archal clone renew <seconds>` | Extend the session lifetime |
113
139
  | `archal clone reset` | Reset clone state without tearing down the session |
114
140
  | `archal clone seed <clone> <name>` | Load a named seed into a running clone |
115
141
  | `archal run [scenario]` | Run a scenario file (or use `--config` for `.archal.json`) |
142
+ | `archal workspace api-keys` | List workspace API keys with an owner/admin user session |
143
+ | `archal workspace api-key create <label>` | Create a workspace API key for CI/runtime auth |
144
+ | `archal workspace api-key revoke <key-id>` | Revoke a workspace API key |
145
+ | `archal autoloop [trace-dir] --repo <dir>` | Register a read-only trace source or start a local trace loop |
146
+ | `archal autoloop status [run-id]` | Show local trace job status or hosted run evidence |
147
+ | `archal autoloop detach <trace-dir>` | Stop a local file-backed autoloop loop |
116
148
  | `archal scenario list` | Browse local and hosted scenarios |
117
149
  | `archal seed list [clone]` | List prebuilt clone seeds |
118
- | `archal trace` | View recent scenario traces |
119
- | `archal trace <name>` | View trace details for a run |
150
+ | `archal traces list` | View recent scenario traces |
151
+ | `archal traces detail <name|id>` | View trace details for a run |
120
152
  | `archal usage` | Check active workspace session-minutes and plan |
121
153
 
122
154
  Run `archal <command> --help` for flag details.
123
155
 
124
- ## Vitest integration (secondary use case)
125
-
126
- You can also import `archal/vitest` to route SDK traffic from a vitest
127
- suite through a hosted clone, with no code changes to your production
128
- code. This is useful if you want to test the HTTP side of an integration
129
- without hitting real provider APIs.
130
-
131
- > The vitest helper supports the hosted route-mode clone catalog: Apify,
132
- > Discord, GitHub, Google Workspace, Jira, Linear, Ramp, Slack, Stripe,
133
- > Supabase, and Tavily.
134
- > If you only need scenario-level evaluation, the CLI flow above is
135
- > simpler to set up.
136
-
137
- ### Minimal config
138
-
139
- ```ts
140
- import { defineConfig } from 'vitest/config';
141
- import { withArchal } from 'archal/vitest';
142
-
143
- export default defineConfig({
144
- test: withArchal(
145
- {
146
- // everything you already had in test:, unchanged
147
- globals: true,
148
- coverage: { provider: 'v8' },
149
- },
150
- {
151
- services: {
152
- stripe: { mode: 'route', seed: 'small-business' },
153
- },
154
- },
155
- ),
156
- });
157
- ```
158
-
159
- `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.
156
+ For terminal-first autonomous loops, see
157
+ <https://docs.archal.ai/guides/autoloop-production-traces>.
160
158
 
161
- Your existing tests work unchanged:
159
+ ## Vitest integration
162
160
 
163
- ```ts
164
- import { it, expect } from 'vitest';
165
- import Stripe from 'stripe';
166
-
167
- it('creates a customer', async () => {
168
- const stripe = new Stripe('sk_test_fake'); // fake key is fine
169
- const customer = await stripe.customers.create({ // goes to clone, not real Stripe
170
- email: 'test@example.com',
171
- });
172
- expect(customer.id).toMatch(/^cus_/);
173
- });
174
- ```
175
-
176
- ### Per-test state isolation
177
-
178
- ```ts
179
- import { beforeEach } from 'vitest';
180
- import { resetArchalClones } from 'archal/vitest';
181
-
182
- beforeEach(async () => {
183
- await resetArchalClones();
184
- });
185
- ```
186
-
187
- ### Webhook testing
188
-
189
- The hosted clone runs in AWS ECS, so it can't POST to your localhost.
190
- Instead, your test pulls queued deliveries with `waitForArchalWebhook()`
191
- and invokes your handler directly with the exact payload the clone would
192
- have sent.
193
-
194
- ```ts
195
- import { it, expect } from 'vitest';
196
- import Stripe from 'stripe';
197
- import { waitForArchalWebhook } from 'archal/vitest';
198
- import { handleStripeWebhook } from './src/webhooks';
199
-
200
- const stripe = new Stripe('sk_test_fake');
201
-
202
- it('records a subscription when customer.subscription.created fires', async () => {
203
- // 1. Register a webhook endpoint (the clone needs to know what events to queue)
204
- await stripe.webhookEndpoints.create({
205
- url: 'http://test.local/stripe-wh',
206
- enabled_events: ['customer.subscription.created'],
207
- });
208
-
209
- // 2. Trigger the event
210
- const customer = await stripe.customers.create({ email: 'wh@x.com' });
211
- const sub = await stripe.subscriptions.create({
212
- customer: customer.id,
213
- items: [{ price: 'price_existing_in_seed' }],
214
- });
215
-
216
- // 3. Pull the queued delivery
217
- const event = await waitForArchalWebhook('stripe', 'customer.subscription.created');
218
- expect(event.payload.data.object.id).toBe(sub.id);
219
-
220
- // 4. Invoke your handler with the exact payload
221
- await handleStripeWebhook(event.body, event.headers['Stripe-Signature'], process.env.STRIPE_WEBHOOK_SECRET);
222
- });
223
- ```
224
-
225
- **Webhook coverage**:
226
-
227
- | Service | Support | Notes |
228
- |---|---|---|
229
- | Stripe | ✅ | |
230
- | GitHub | ✅ | |
231
- | Slack | ✅ | Receiver-side signature only |
232
- | Jira | ✅ | Delivered via history buffer (also POSTed to registered URLs) |
233
- | Linear | ✅ | Delivered via history buffer (also POSTed to registered URLs) |
234
- | Supabase | ❌ | Database-triggered; test against real Postgres |
235
- | Google Workspace | ❌ | GCP Pub/Sub push notifications, not webhooks |
236
-
237
- **Parallel workers caveat**: `waitForArchalWebhook()` consumes deliveries
238
- from a shared queue by default. When vitest runs test files in parallel
239
- across workers, worker A can swallow worker B's event. If your tests
240
- depend on webhook events, set `testIsolation: 'serial'` in your config.
241
-
242
- ### Test isolation across parallel workers
243
-
244
- Each vitest worker is routed to its own per-worker state on the clone,
245
- so parallel tests across workers don't see each other's writes. The
246
- integration reads `VITEST_WORKER_ID` in each worker process and tags
247
- every outbound SDK request with an `X-Archal-Worker-Id` header. The clone
248
- maintains a separate state engine per worker id, seeded from the
249
- baseline on first request.
250
-
251
- **Isolation-enabled clones**: Stripe, GitHub, Slack, Jira, Linear.
252
-
253
- **Clones without isolation** (Supabase, Google Workspace, Ramp) fall back
254
- to shared state. If your tests depend on global assertions against those
255
- clones, set `testIsolation: 'serial'`.
161
+ The package also exposes `archal/vitest` for routing SDK traffic from a Vitest
162
+ suite through hosted clones. The stable guide lives at
163
+ <https://docs.archal.ai/guides/vitest>.
256
164
 
257
165
  ## Authentication
258
166
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "description": "The real OpenClaw gateway agent (successor to the legacy --sandbox path).",
3
+ "agent": {
4
+ "command": "node",
5
+ "args": ["drive.mjs"]
6
+ },
7
+ "clones": ["github"]
8
+ }
@@ -0,0 +1,97 @@
1
+ # OpenClaw agent harness — runs the real OpenClaw agent against Archal clones.
2
+ #
3
+ # This is the packaged-agent successor to the legacy `--sandbox` path. Instead of
4
+ # a fixed `archal/sandbox` image with a baked-in proxy + entrypoint, OpenClaw runs
5
+ # here as an ordinary packaged agent through the generic Docker-harness sidecar
6
+ # engine (the sidecar owns network interception).
7
+ #
8
+ # The SIDECAR — not this image — owns all network interception: DNS rewrites, the
9
+ # TLS MITM listener, the CA, and the agent-egress seal. This image therefore runs
10
+ # NO in-container proxy, NO `/etc/hosts` rewrite, and NO iptables. The agent keeps
11
+ # calling the real service domains (e.g. api.github.com); the sidecar transparently
12
+ # routes that traffic to the seeded clone, and `api.openai.com` /
13
+ # `api.anthropic.com` are forwarded to the real model with the host key injected by
14
+ # the proxy. The sidecar writes its CA to /agent-output/ca.crt and the harness sets
15
+ # NODE_EXTRA_CA_CERTS to it, so child processes trust the intercept automatically.
16
+ FROM node:22-bookworm-slim
17
+
18
+ # Pin the agent version. Override at build time:
19
+ # --build-arg OPENCLAW_VERSION=2026.6.6
20
+ # When no build-arg is passed, the pinned version is read from the colocated
21
+ # package.json `dependencies.openclaw`, which Dependabot's npm updater keeps
22
+ # current (see .github/dependabot.yml). Do NOT use a floating tag — the agent is
23
+ # the highest-blast-radius surface and `latest` is a supply-chain attack surface.
24
+ ARG OPENCLAW_VERSION=
25
+
26
+ ENV OPENCLAW_DISABLE_BONJOUR=1
27
+
28
+ # System tools OpenClaw's shell/exec tools expect:
29
+ # ca-certificates - lets `update-ca-certificates` consume the sidecar CA
30
+ # curl - gateway health checks + agent HTTP calls
31
+ # git - required by gh and common agent workflows
32
+ # jq - JSON shaping in agent shell steps
33
+ # ripgrep - fast source/search tool used by coding agents
34
+ # gh (GitHub CLI) is installed from the official apt repo below.
35
+ RUN apt-get update \
36
+ && apt-get install -y --no-install-recommends \
37
+ ca-certificates \
38
+ curl \
39
+ git \
40
+ jq \
41
+ ripgrep \
42
+ && rm -rf /var/lib/apt/lists/*
43
+
44
+ # GitHub CLI from the official apt repo. OpenClaw reaches GitHub clones by
45
+ # shelling out to `gh` (and direct curl to api.github.com) — there is no GitHub
46
+ # MCP server in this harness.
47
+ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
48
+ | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
49
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
50
+ > /etc/apt/sources.list.d/github-cli.list \
51
+ && apt-get update \
52
+ && apt-get install -y gh \
53
+ && rm -rf /var/lib/apt/lists/*
54
+
55
+ # Install the agent and run its bundled-plugin postinstall so the stock
56
+ # extensions (browser, etc.) materialize under dist/extensions. Fail the build
57
+ # loudly if the extension root is missing.
58
+ #
59
+ # Copy the version manifest to a temp path so it is available to the install RUN
60
+ # below; the RUN deletes it so it never lands in the shipped image.
61
+ COPY package.json /tmp/openclaw-pin/package.json
62
+ # Version source of truth: an explicit `--build-arg OPENCLAW_VERSION` wins; otherwise
63
+ # the version pinned in package.json (Dependabot-managed) is used. Docker expands the
64
+ # `${OPENCLAW_VERSION:-…}` default, and the `$(node -p …)` runs in the shell. This is a
65
+ # SINGLE reference on purpose: Docker textually substitutes every `${OPENCLAW_VERSION}`
66
+ # in a RUN with the build-arg value before the shell runs, so a second reference (or a
67
+ # reused shell var of the same name) would be clobbered. A missing/malformed pin yields
68
+ # `openclaw@undefined` / `openclaw@`, which npm rejects loudly — never a floating install.
69
+ RUN npm install -g "openclaw@${OPENCLAW_VERSION:-$(node -p "require('/tmp/openclaw-pin/package.json').dependencies.openclaw")}" \
70
+ && node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs \
71
+ && test -d /usr/local/lib/node_modules/openclaw/dist/extensions \
72
+ && rm -rf /tmp/openclaw-pin
73
+
74
+ # Pre-configure the gh CLI with a format-valid dummy token. The sidecar replaces
75
+ # the Authorization header on every forwarded request with supervisor-owned
76
+ # credentials, so this token only needs to pass gh's local format check — a
77
+ # `gho_` prefix makes gh treat it as a valid OAuth token. DO NOT set GH_TOKEN:
78
+ # it takes precedence over hosts.yml and gh validates it with a direct API call
79
+ # that bypasses the proxy's header replacement.
80
+ RUN mkdir -p /root/.config/gh \
81
+ && printf '%s\n' \
82
+ 'github.com:' \
83
+ ' oauth_token: gho_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTt' \
84
+ ' user: workflow-bot' \
85
+ ' git_protocol: https' \
86
+ > /root/.config/gh/hosts.yml
87
+
88
+ WORKDIR /app
89
+
90
+ # The drive entrypoint + the agent's persona/workspace assets. The drive script
91
+ # copies the workspace into ~/.openclaw/workspace at boot.
92
+ COPY drive.mjs /app/drive.mjs
93
+ COPY extract-openclaw-response-text.mjs /app/extract-openclaw-response-text.mjs
94
+ COPY workspace/ /app/workspace/
95
+
96
+ # The .archal.json launch command overrides this; kept for standalone debugging.
97
+ CMD ["node", "/app/drive.mjs"]
@@ -0,0 +1,113 @@
1
+ # OpenClaw Agent Harness
2
+
3
+ This example runs the **real OpenClaw agent** against an Archal clone, packaged as
4
+ an ordinary agent and executed through the generic Docker-harness **sidecar**
5
+ engine.
6
+
7
+ It is the agent behind `archal run <scenario>.md --sandbox`. The `--sandbox` flag
8
+ no longer runs a bespoke in-container engine: the legacy path — a fixed
9
+ `archal/sandbox` image with an in-container TLS proxy, DNS rewrites, a baked
10
+ entrypoint, and the `runSandboxed` special case — has been removed. `--sandbox`
11
+ now launches this packaged agent through the generic Docker-harness **sidecar**,
12
+ which owns all network interception while this image just runs the agent.
13
+
14
+ > **Status: live.** `archal run <scenario>.md --sandbox` resolves to this bundled
15
+ > package. There is no separate `--agent` flag — the packaged-agent selector was
16
+ > removed; other packaged agents run via `--harness <dir> --dockerfile <dir>/Dockerfile`.
17
+
18
+ ## What this demonstrates
19
+
20
+ - A full external agent (the OpenClaw gateway + its shell/exec tools) packaged
21
+ into one image, driven once per task and printing its answer to stdout.
22
+ - **Transparent interception by the sidecar**: the agent's `gh` / `curl` calls to
23
+ `api.github.com` are routed to the clone via the sidecar's DNS + TLS MITM — no
24
+ base-URL override, no code change. The sidecar writes its CA to
25
+ `/agent-output/ca.crt`; the harness sets `NODE_EXTRA_CA_CERTS` to it, so the CA
26
+ is trusted by the agent and its child processes.
27
+ - **No in-container network plumbing**: this image runs **no** proxy, **no**
28
+ `/etc/hosts` rewrite, and **no** iptables. That was the old single-container
29
+ model; the sidecar replaces it.
30
+ - **Real model, fake services**: provider domains (`api.openai.com`,
31
+ `api.anthropic.com`, …) are forwarded to the real model with the host key
32
+ injected by the proxy; only clone domains are intercepted.
33
+ - **Read-only behavior** scored from the clone trace plus the agent's answer text.
34
+
35
+ ## Files
36
+
37
+ | File | Purpose |
38
+ |------|---------|
39
+ | `Dockerfile` | Packages `openclaw` (version pinned in `package.json`, overridable via `--build-arg OPENCLAW_VERSION`) + the `gh` CLI |
40
+ | `package.json` | Dependabot-watched version pin for `openclaw` (not a pnpm-workspace package) |
41
+ | `drive.mjs` | Entrypoint: reads `AGENT_TASK`, starts the local gateway, sends the task, prints the answer to stdout |
42
+ | `extract-openclaw-response-text.mjs` | Shared Responses-API answer parser (canonical copy in `packages/sandbox-runtime`) |
43
+ | `workspace/` | A **generic demo persona** (`IDENTITY.md`, `SOUL.md`, `AGENTS.md`, `TOOLS.md`) — swap or mount your agent's real persona to run it as itself |
44
+ | `.archal.json` | Declares the agent command + the `github` clone |
45
+ | `scenarios/` | GitHub triage and live-acceptance scenarios |
46
+
47
+ ## How `drive.mjs` works
48
+
49
+ It reproduces the **agent-side** of the legacy sandbox entrypoint; the
50
+ **network-side** (proxy, CA install, DNS, iptables) is the sidecar's job and is
51
+ intentionally absent here. The drive script:
52
+
53
+ 1. Stages the bundled `workspace/` into a writable `~/.openclaw/workspace` and
54
+ writes a minimal non-interactive `~/.openclaw/openclaw.json` (local gateway on
55
+ `:18789`, provider base URLs at their real defaults with `allowPrivateNetwork`,
56
+ shell/exec tools allowed).
57
+ 2. Starts the gateway: `openclaw gateway run --port 18789 --bind loopback`, and
58
+ waits for the `[gateway] ready` marker.
59
+ 3. Sends the task to that gateway:
60
+ `openclaw agent --agent main --session-id <id> --message "$AGENT_TASK" --timeout <s> --json`.
61
+ 4. Parses the agent's final answer via `extract-openclaw-response-text.mjs` and
62
+ prints it to stdout for the evaluator.
63
+
64
+ ## Syntax check
65
+
66
+ ```bash
67
+ node --check drive.mjs
68
+ ARCHAL_PREFLIGHT=1 node drive.mjs # only meaningful inside the built image
69
+ ```
70
+
71
+ ## Run
72
+
73
+ `archal run <scenario>.md --sandbox` is the one-flag shortcut for this bundled
74
+ package. To run it explicitly as a packaged agent — the same path `--sandbox`
75
+ resolves to internally — point the generic harness flags at this directory:
76
+
77
+ ```bash
78
+ cd examples/agents/openclaw
79
+ # Set the key for the model's provider (see the env-var table below):
80
+ # OPENAI_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY.
81
+ export OPENAI_API_KEY=...
82
+ archal run scenarios/github-issue-triage-read-only.md \
83
+ --harness . \
84
+ --dockerfile Dockerfile \
85
+ -n 1
86
+ ```
87
+
88
+ Docker mode is required so the sidecar can control DNS and TLS trust for
89
+ `api.github.com`.
90
+
91
+ ## Build args
92
+
93
+ | Arg | Default | Notes |
94
+ |-----|---------|-------|
95
+ | `OPENCLAW_VERSION` | from `package.json` (`dependencies.openclaw`, Dependabot-managed) | Pin the agent version. Defaults to the pinned `package.json` version; override with `docker build --build-arg OPENCLAW_VERSION=2026.6.6 ...` or the equivalent harness option. Do not use a floating tag. |
96
+
97
+ ## Environment variables
98
+
99
+ | Variable | Source | Notes |
100
+ |----------|--------|-------|
101
+ | `AGENT_TASK` | Injected by Archal | The scenario prompt |
102
+ | `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` | Host → proxy | Real key on the host; the sidecar injects the matching provider's native auth header. Each provider is pinned to its native wire API (`openai-responses` / `anthropic-messages` / `google-generative-ai`), so non-OpenAI models hit their native paths (`/v1/messages`, `:generateContent`) instead of OpenAI-compat `/chat/completions`. |
103
+ | `AGENT_MODEL` | Injected by Archal (optional) | Overrides the gateway's default model. Use a provider-prefixed id (e.g. `anthropic/claude-sonnet-4-6`, `google/gemini-2.5-flash`) |
104
+ | `AGENT_ID` | Optional | Selects the agent (default `main`) |
105
+ | `AGENT_DISABLE_PLUGINS` / `AGENT_EVAL_MODE=isolated` | Optional | Eval mode — runs with an isolated config and no business-tool plugins (GitHub is reached via the `gh` CLI, not a plugin) |
106
+ | `ARCHAL_TIMEOUT` | Optional | Per-task agent timeout in seconds (default `120`) |
107
+
108
+ ## Read-only home / workspace
109
+
110
+ The demo persona is shipped read-only-friendly: `drive.mjs` copies the bundled
111
+ `workspace/` into a writable `~/.openclaw/workspace`, so the source assets can be
112
+ mounted **read-only**. Pass `--openclaw-home` to mount your own OpenClaw home at
113
+ `/openclaw-home` inside the container.