Step 1: handshake with capabilities
Ask the engine what it speaks before you assume anything:value is:
envelopeSchema and readySchemaVersion against what your parser expects. outputJsonCommands is read off the live registrations, so it never drifts from the release that you run.
Step 2: start a session with machine output
--allow-control unlocks the bounded act verbs (storage, reload, open, inspect). Add --allow-eval only when you need extension eval, it runs arbitrary code and writes a 0600 session token.
Under --output json the command prints one started envelope, and failures arrive as envelopes too. Human copy moves to stderr, so stdout stays parseable.
To follow the whole session frame by frame, set EXTENSION_OUTPUT=ndjson in the environment and read the lifecycle stream.
Step 3: gate on runtime attached
Readiness has two phases, and acting too early is the most common agent failure:ready.jsonreportsstatus: "ready". This means compiled, nothing more.- The contract carries
runtime: "attached"andexecutorAttachedAt. Now the service worker is connected and can be driven.
dist/extension-js/chromium/ready.json until runtime equals "attached" before you run any act verb. See ready.json for the freshness and pid-liveness rules that keep you off stale contracts.
Step 4: act and read envelopes
Every act verb takes--output json and answers with one schema-1 result envelope:
ok and error.code, never on error.message. The error code table is stable across releases.
Discover the CLI with —ai-help
extension --ai-help --output json prints a machine-readable self-description. Two mechanics matter to a caller:
--ai-helpbypasses the argument parser. It runs before any subcommand parsing, so it works with otherwise-invalid invocations.- The payload can outgrow one pipe buffer, so the process drains stdout before it exits. Read until EOF.
The
capabilities.readyContract block names the contract paths, statuses, fields, and event types, so an agent can self-configure without this docs site.
Rules that keep agents honest
- Never parse pretty terminal output. Every fact that you need has a machine surface.
- Verify
pidliveness and contract freshness before trustingready.json. - Join
ready.json,events.ndjson, andlogs.ndjsononrunId. - Use
browserPidfromready.jsonto tear the browser down, never process-name matching. - Treat unknown envelope fields as additive. The schema grows, it does not break.
Next steps
- Keep the contract details open: ready.json and Result envelope.
- Wire the same loop into CI with CI templates.

