# How it works

> Understand Blop's observe, investigate and verify loop, where each kind of evidence is stored, and the JSON contract agents rely on.

Blop collects evidence on your machine first and adds a browser check only when a question needs one. The loop is observe, investigate, verify.

```mermaid
flowchart LR
  accTitle: Local telemetry: observe, investigate, verify
  accDescr: Apps and coding agents send OTLP telemetry and hook events to the local collector store. Investigate with status, traces, logs and audit, then reproduce and audit again after a change.
  source["Observe<br/>App and<br/>coding agent"] -->|"OTLP / hooks"| collector["Local<br/>collector store"]
  collector --> investigate["Investigate<br/>Status, traces,<br/>logs and audit"]
  investigate --> verify["Verify<br/>Reproduce,<br/>audit again"]
```

```mermaid
flowchart LR
  accTitle: Browser checks: observe, investigate, verify
  accDescr: A browser check produces a check run, stored separately in Blop Cloud when uploaded. Investigate its steps, assertions and artifacts, then rerun the check to verify.
  check["Observe<br/>Browser check<br/>(blop test)"] --> run["Check run<br/>Blop Cloud<br/>when uploaded"]
  run --> investigate["Investigate<br/>Steps, assertions,<br/>artifacts"]
  investigate --> verify["Verify<br/>Rerun the check"]
```

Text alternative: your app and coding agents send telemetry to the local collector, where you query and audit it, then reproduce and audit again after a change; a browser check produces a check run, kept apart in Blop Cloud when uploaded, and you rerun the check to verify.

## Observe: send telemetry to the collector

```bash
blop up --detach
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:27811 pnpm test
```

Use the URL that `blop up` prints if it differs. The collector accepts standard OTLP/HTTP traces and logs, as JSON or protobuf. The workload must already be instrumented with an OpenTelemetry SDK; setting the endpoint alone emits nothing.

Two inputs cover two sides of the work:

- **Hooks** capture the coding agent itself: prompts, tool calls, tool errors and turn boundaries, read from the agent's own lifecycle events. See [Connect a coding agent](/guides/connect-agent/).
- **OpenTelemetry** captures your application: its traces, spans and logs, including requests an agent caused.

Neither is inferred from the other.

## Investigate: ask the smallest question first

```bash
blop traces --service checkout --status error --since 15m --json
blop trace <trace-id> --include logs --json
blop audit --service checkout --since 15m --json
```

Start from a compact summary and drill into one service, trace, span or log set. Results are bounded, so a query never dumps the whole store into an agent's context. `blop audit` runs a fixed analysis over stored telemetry, looking for error traces, new error signatures, latency regressions and log bursts. The same data always gives the same findings, each with trace and span IDs. For a browser check, read its failing step, assertion and artifacts. See [Investigate](/guides/investigate/).

## Verify: reproduce, then audit again

```bash
blop audit --service checkout --since 15m --fail-on regression --json
```

After a change, reproduce the behaviour and rerun the same queries or audit. `--fail-on regression` exits 1 when the audit finds a regression. If the claim is about a user-visible flow, rerun the browser check:

```bash
blop test tests/checkout.blop.ts --base-url http://localhost:3000
```

Run checks against local, preview or staging targets. A production target must be chosen deliberately; read [Production checks](/checks/production/) first. A healthy collector or an installed hook is not proof that a flow succeeded.

## Choose the evidence source

| Question | Start with |
| --- | --- |
| Which service or span failed? | Local traces, spans and their logs |
| Did latency or errors regress after this change? | `blop audit` against a baseline |
| Can a user complete this exact flow? | A browser check |
| What did the browser check see and decide? | The check run's steps, assertions and artifacts |
| Did an existing Vitest or JUnit suite fail? | Check runs uploaded to Blop Cloud by the test adapter |

## Where evidence is stored

| | Local collector store | Blop Cloud |
| --- | --- | --- |
| Holds | Traces, spans, logs, services, coding-agent sessions, runs (`blop runs`) and issues | Check runs, projects and organization memory |
| Lives | On your machine | In your team's workspace |
| Input | OTLP/HTTP traces and logs, coding-agent hooks | Browser checks and test adapters |
| Read with | `blop` query and audit commands, the Inspector | Blop Cloud check run views |

A run is not a trace: a run records one command that `blop run` started, and the traces it produced are stored beside it. Blop never copies the local store into Blop Cloud and never joins the two stores; an agent can still use both in one investigation.

The local store keeps telemetry for 720 hours (30 days), up to 1024 MB, deleting the oldest first. `blop status` shows its path, and `BLOP_MONITOR_DB_PATH` overrides it.

## Safety in a coding loop

- The collector listens on `127.0.0.1` by default. Its query and ingestion routes have no authentication or TLS, so do not expose the port.
- Request bodies and result sizes are capped.
- The collector never runs application code, agent commands or browser actions.
- It keeps only what your app or agent sends as telemetry, never environment variables, source files or headers on its own.

## JSON output contract

Every query takes `--json`. In JSON mode a command prints exactly one `blop.cli/v1` document on stdout, on success and on failure, never prompts, and sends progress and notes to stderr:

```json
{
  "schema_version": "blop.cli/v1",
  "command": "traces",
  "generated_at": "2026-11-03T09:46:53.891Z",
  "ok": true,
  "data": [],
  "page": { "limit": 20, "next_cursor": null },
  "warnings": [],
  "next": []
}
```

- `ok` is true exactly when the exit code is 0. The one exception is `blop run`: `ok` is true when the wrapped command started, and that command's exit code is in `data.exit_code`.
- `warnings` holds `{code, message}` notes; `next` holds `{command, when}` steps.
- On failure, `error` carries a stable `code` to branch on, plus `message` and `hint`.
- Field names are snake_case and timestamps are ISO 8601 UTC.

`blop run --json` writes its document to stderr, because the wrapped command owns stdout. `blop schema` prints the JSON Schema.

Branch on the exit code (`blop help exit-codes` prints them all):

| Code | Meaning |
| --- | --- |
| 0 | Success |
| 1 | The command reported a failure, such as a `--fail-on` threshold or a failed doctor check |
| 2 | Usage error; nothing was sent |
| 3 | Blop is not running; start it with `blop up` |
| 4 | The collector rejected the query |
| 5 | The collector did not answer in time |
| 6 | Could not verify (`blop init`, `blop setup mcp`) |
| 78 | Store unrecoverable or incompatible. Do not retry; run `blop up --recover` |
| 127 | `blop run`'s command or a launcher's agent binary was not found |
| 129, 130, 143 | Stopped by SIGHUP, Ctrl-C or SIGTERM |

`blop run` and the launchers (`blop claude`, `blop codex`, `blop opencode`) exit with the child's own code.

See [JSON output](/reference/cli/#json-output) and [exit codes](/reference/cli/#exit-codes) for every field and code.
