{
  "stage": "recon",
  "source": {
    "target": "https://example.com",
    "mode": "headful",
    "timeout_ms": 20000,
    "cookies": null
  },
  "traces": {
    "requested": 57,
    "failed": 1,
    "cdp_events": [
      "Network.requestWillBeSent",
      "Network.responseReceived"
    ]
  }
}

Recon → IR → Executor pipeline

Goal: one LLM pass during recon, then zero runtime tokens in runtime execution.

1) Recon Capture request/response, page graph, and interaction hints in a single browser pass.
2) Classifier + Serde IR Normalize captured signals into typed Rust models and freeze output schema.
3) Executor translation Emit `ExecutorConfig`-compatible action targets for downstream runtimes.
Websocket-free path
No runtime LLM
Deterministic replay

Rust serde pattern catalog

Internal tagging selected for IR legibility and maintainer ergonomics.

Externally tagged enums

Minimal surface area for external APIs where a stable one-level wrapper is acceptable

Rust shape
{"type":"navigate","args":{"url":"https://example.com"}}

Simple for docs and logs but adds one extra object layer; less human-scannable in diff-heavy specs.

Internally tagged enums

Human-readable SiteDescriptor documents, as used by webctl IR blocks

Rust shape
{"kind":"Navigate","url":"https://example.com"}

Selected for IR legibility because discriminant stays co-located with fields.

Adjacent tagged enums

Intermediates where both wrapper and type metadata are useful

Rust shape
{"op":"navigate","payload":{"url":"https://example.com"}}

Useful for protocol versioning but heavier than needed for baseline executor targets.

Untagged enums

Compact wire format when strict schema disambiguation is impossible

Rust shape
{"url":"https://example.com","selector":"main"}

Not suitable as default for public IR because ambiguity increases parser risk.

ExecutorConfig compatibility notes

Maintainer split

Maintainers own recon and IR canonicalization in webctl. Executors consume a normalized IR without needing web inference at runtime.

Consumer split

Runtime binaries focus on applying deterministic commands: navigation, click, typing, waits, assertions, and outputs.

Rust CLI architecture sketch

A[Input URLs + policy] --> B[Recon Engine: Chromium CDP bridge]
B --> C[Signal classifier + feature extractor]
C --> D[Serde IR builder]
D --> E[Schema validator + diff guard]
E --> F[ExecutorConfig adapter]
F --> G[runner (just-bash compatible)]

Implementation direction: keep recon module owned by webctl and keep runner path pure and low dependency.