# ObservMe Configuration Reference

## 1. Full Example

```yaml
observme:
  enabled: true
  environment: production
  tenant: platform

  otlp:
    endpoint: https://otel-collector.example.com:4318   # base OTLP HTTP endpoint
    protocol: http/protobuf
    timeoutMs: 3000
    headers:
      Authorization: "Bearer ${OBSERVME_OTLP_TOKEN}"
    tls:
      insecureSkipVerify: false
    # The endpoint scheme selects HTTP or HTTPS. HTTPS certificate verification
    # stays enabled unless insecureSkipVerify is explicitly acknowledged.
    # Derived SDK URLs for OTLP/HTTP exporters:
    # traces:  https://otel-collector.example.com:4318/v1/traces
    # metrics: https://otel-collector.example.com:4318/v1/metrics
    # logs:    https://otel-collector.example.com:4318/v1/logs

  resource:
    attributes:
      service.name: observme-pi-extension
      observme.tenant.id: platform
      pi.project.name: my-project
      deployment.environment.name: production

  workflow:
    idEnv: OBSERVME_WORKFLOW_ID
    enabled: true
    maxDepthWarning: 5
    maxFanoutWarning: 20

  agent:
    # Generated when absent. Parent/root values are accepted only from trusted
    # process environment or explicit runtime options.
    idEnv: OBSERVME_AGENT_ID
    parentIdEnv: OBSERVME_PARENT_AGENT_ID
    rootIdEnv: OBSERVME_ROOT_AGENT_ID
    parentSessionIdEnv: OBSERVME_PARENT_SESSION_ID
    parentTraceIdEnv: OBSERVME_PARENT_TRACE_ID
    parentSpanIdEnv: OBSERVME_PARENT_SPAN_ID
    depthEnv: OBSERVME_AGENT_DEPTH
    spawnIdEnv: OBSERVME_SPAWN_ID
    propagateTraceContext: true
    propagateToSubagents: true
    childIdentityEnvelopeVersionEnv: OBSERVME_CHILD_IDENTITY_ENVELOPE_VERSION
    displayNameEnv: OBSERVME_AGENT_DISPLAY_NAME
    roleEnv: OBSERVME_AGENT_ROLE
    capabilityEnv: OBSERVME_AGENT_CAPABILITY
    writeCorrelationEntry: false

  traces:
    enabled: true
    sampleRatio: 1.0
    batch:
      maxQueueSize: 2048
      maxExportBatchSize: 512
      scheduledDelayMillis: 1000
      exportTimeoutMillis: 3000

  metrics:
    enabled: true
    exportIntervalMillis: 15000
    exportTimeoutMillis: 3000
    activeAgentLeaseDurationMillis: 60000
    # Reserved compatibility field: names are validated, but current recorders do
    # not add configured labels to measurements.
    labels: []

  logs:
    enabled: true
    batch:
      maxQueueSize: 2048
      maxExportBatchSize: 512
      scheduledDelayMillis: 1000

  capture:
    prompts: false
    responses: false
    thinking: false
    toolArguments: false
    toolResults: false
    bashCommands: false
    bashOutput: false
    filePaths: false  # reserved; no direct live file-path recording point

  privacy:
    redactionEnabled: true
    allowUnsafeCapture: false
    allowInsecureTransport: false
    tenantSaltEnv: OBSERVME_HASH_SALT
    pathMode: hash
    customRedactionPatterns:
      - name: internal-token
        pattern: "(?i)internal_token=[a-z0-9-]+"

    # Custom redaction is fail-closed: at most 16 patterns, 64 characters
    # per normalized name, 256 characters per expression, 4,096 total
    # matches, and 1,000,000 intermediate output characters per value.

  limits:
    maxPromptChars: 12000
    maxResponseChars: 12000
    maxToolArgumentChars: 8000
    maxToolResultChars: 16000
    maxBashOutputChars: 16000
    maxLogBodyChars: 32000
    maxActiveAgentRuns: 16
    maxActiveTurns: 128
    maxActiveToolCalls: 1024
    maxActiveLlmRequests: 128
    maxActiveSubagentSpawns: 128
    maxActiveAgentWaits: 128
    maxActiveAgentJoins: 128

  query:
    enabled: true
    timeoutMs: 5000
    maxLogs: 50
    maxTraces: 20
    maxMetricSeries: 20
    maxAgents: 20
    links:
      traceUrlTemplate: "https://grafana.example.com/explore?left=..."
    grafana:
      url: https://grafana.example.com
      token: ${OBSERVME_GRAFANA_TOKEN}
      username: ""
      password: ""
      datasourceUids:
        tempo: tempo
        loki: loki
        prometheus: mimir
      tls:
        insecureSkipVerify: false
      transport:
        preferIPv4: false

  shutdown:
    flushTimeoutMs: 3000
```

`otlp.endpoint` and every `otlp.signalEndpoints` value must be an absolute HTTP(S) URL without userinfo, unresolved placeholders, a query, or a fragment; credentials belong in `otlp.headers`. The base endpoint may contain an intentional path, to which ObservMe appends exactly one `/v1/{signal}` suffix using URL pathname semantics. Explicit signal endpoints must already end in their matching `/v1/traces`, `/v1/metrics`, or `/v1/logs` path.

`query.grafana.url` must be an absolute HTTP(S) base URL without a username or password component; path-prefixed URLs such as `https://grafana.example.com/observability/team-a/` are supported. Blank values map to `missing_grafana_url` (`missing_url`), unresolved environment placeholders map to `unresolved_grafana_url` (`unresolved_placeholder`), malformed and relative values map to `invalid_grafana_url` (`malformed_url`), and non-HTTP(S) values such as `file:` and `ftp:` map to `invalid_grafana_url` (`unsupported_protocol`). Authentication belongs only in `query.grafana.token` or the complete `query.grafana.username` and `query.grafana.password` pair; URL userinfo maps to `embedded_grafana_url_credentials` (`embedded_credentials`). Primary config loading, `/obs status`, readiness, `/obs health`, and query/transport preflight use this same bounded, value-free policy before network I/O.

`otlp.tls.enabled` is intentionally unsupported and rejected as an unknown setting: the `http://` or `https://` endpoint scheme is the single source of truth for whether TLS is used. The retained `otlp.tls.insecureSkipVerify` option is passed to every OTLP HTTP exporter as its certificate-verification behavior.

`query.links.traceUrlTemplate` is shared by `/obs session`, `/obs trace`, and `/obs link`. Absolute HTTP(S) templates support `{traceId}`, `{{traceId}}` (with optional inner whitespace), `${traceId}`, and `%TRACE_ID%`; optional Tempo UID placeholders use the matching `{tempoDatasourceUid}`, `{{tempoDatasourceUid}}`, `${tempoDatasourceUid}`, and `%TEMPO_DATASOURCE_UID%` forms. An empty template or one containing `...` selects the structured Grafana Explore fallback built from `query.grafana.url` and `query.grafana.datasourceUids.tempo`. Unsupported placeholders and invalid URLs fail with a bounded diagnostic.

`query.maxLogs`, `query.maxTraces`, `query.maxMetricSeries`, and `query.maxAgents` accept integers from 1 through 100. Query clients enforce the same upper bound at runtime before issuing backend requests.

The top-level `environment` accepts only `production`, `development`, or `test`. It drives production transport validation and the `environment` metric label. In YAML it does not automatically rewrite `resource.attributes["deployment.environment.name"]`; set both consistently. `OBSERVME_ENVIRONMENT` maps to both fields. Likewise, the top-level YAML `tenant` field has no separate live routing/telemetry read; set `resource.attributes["observme.tenant.id"]`. `OBSERVME_TENANT` maps to both values.

`workflow.maxDepthWarning` currently clamps the low-cardinality `subagent_depth` metric label; it does not emit a UI warning. `workflow.maxFanoutWarning` is accepted and configurable but has no current live read after config loading.

`metrics.labels` is currently a reserved compatibility field. Config validation rejects forbidden high-cardinality names in the array, but live metric recorders do not add configured labels to measurements.

`capture.filePaths` is currently a reserved compatibility setting. It is accepted, can be overridden by `OBSERVME_CAPTURE_FILE_PATHS`, and appears in `/obs status`, but no live handler uses it to export a direct path field. `privacy.pathMode` independently scrubs recognized absolute paths embedded in prompts, responses, tool data, or Bash content when those fields are enabled. `pathMode: full` preserves those embedded paths regardless of `capture.filePaths`.

The redaction helper includes a PII-detector stage for programmatic callers, but the current `ObservMeConfig` schema has no PII setting and live content capture does not inject a detector. Built-in secret, path, custom-pattern, truncation, and salted-hash stages remain active; do not describe PII removal as a live guarantee.

## 2. Environment Variables

### 2.1 Configuration overrides

These names are mapped by `src/config/load-config.ts` and can come from a trusted project `.env` or the process environment:

```text
OBSERVME_ENABLED
OBSERVME_ENVIRONMENT
OBSERVME_TENANT
OBSERVME_OTLP_ENDPOINT
OBSERVME_OTLP_PROTOCOL
OBSERVME_OTLP_TRACES_ENDPOINT
OBSERVME_OTLP_METRICS_ENDPOINT
OBSERVME_OTLP_LOGS_ENDPOINT
OBSERVME_OTLP_TIMEOUT_MS
OBSERVME_OTLP_TOKEN
OBSERVME_ACTIVE_AGENT_LEASE_DURATION_MS
OBSERVME_WORKFLOW_MAX_DEPTH_WARNING
OBSERVME_WORKFLOW_MAX_FANOUT_WARNING
OBSERVME_PROPAGATE_TRACE_CONTEXT
OBSERVME_PROPAGATE_TO_SUBAGENTS
OBSERVME_WRITE_CORRELATION_ENTRY
OBSERVME_GRAFANA_URL
OBSERVME_GRAFANA_TOKEN
OBSERVME_GRAFANA_USERNAME
OBSERVME_GRAFANA_PASSWORD
OBSERVME_GRAFANA_TEMPO_DATASOURCE_UID
OBSERVME_GRAFANA_LOKI_DATASOURCE_UID
OBSERVME_GRAFANA_PROMETHEUS_DATASOURCE_UID
OBSERVME_GRAFANA_TLS_INSECURE_SKIP_VERIFY
OBSERVME_GRAFANA_PREFER_IPV4
OBSERVME_CAPTURE_PROMPTS
OBSERVME_CAPTURE_RESPONSES
OBSERVME_CAPTURE_TOOL_ARGUMENTS
OBSERVME_CAPTURE_TOOL_RESULTS
OBSERVME_CAPTURE_THINKING
OBSERVME_CAPTURE_BASH_COMMANDS
OBSERVME_CAPTURE_BASH_OUTPUT
OBSERVME_CAPTURE_FILE_PATHS
OBSERVME_REDACTION_ENABLED
OBSERVME_ALLOW_UNSAFE_CAPTURE
OBSERVME_ALLOW_INSECURE_TRANSPORT
```

`OBSERVME_HASH_SALT` is not a config-field override. It is the default secure salt input selected by `privacy.tenantSaltEnv` and is required when the redaction pipeline hashes captured content.

### 2.2 Launcher-only propagation envelope

These names belong to the trusted parent/child process boundary, not to project `.env` configuration values:

```text
OBSERVME_WORKFLOW_ID
OBSERVME_AGENT_ID
OBSERVME_PARENT_AGENT_ID
OBSERVME_ROOT_AGENT_ID
OBSERVME_PARENT_SESSION_ID
OBSERVME_PARENT_TRACE_ID
OBSERVME_PARENT_SPAN_ID
OBSERVME_AGENT_DEPTH
OBSERVME_SPAWN_ID
OBSERVME_CHILD_IDENTITY_ENVELOPE_VERSION
OBSERVME_AGENT_DISPLAY_NAME
OBSERVME_AGENT_ROLE
OBSERVME_AGENT_CAPABILITY
traceparent
tracestate
```

An ObservMe-aware launcher should pass the environment returned by `startSubagent()` rather than setting these values itself. In particular, a child envelope must not inherit `OBSERVME_AGENT_ID`; the child generates its own agent ID. `OBSERVME_AGENT_ID` is retained as an internal configurable environment-name contract for validated recovery/runtime contexts. Configuring the three new child-identity key names does not by itself emit an identity marker, display name, or role.

### 2.3 Active-agent lease configuration

| Setting | Default | Supported values | Purpose |
| --- | --- | --- | --- |
| `metrics.activeAgentLeaseDurationMillis` | `60000` | Integer `10000`–`300000`, and at least `(2 * metrics.exportIntervalMillis) + 5000` | Validity window renewed on each metric collection while the session is active. |
| `OBSERVME_ACTIVE_AGENT_LEASE_DURATION_MS` | unset | Same as the YAML field | Environment override through the normal trusted `.env` / system environment precedence. |

The value controls failure-convergence latency, not a background timer. A clean shutdown deactivates the lease before final flush and reaches zero after normal export/scrape propagation. An ungraceful exit reaches zero within the configured lease plus up to 5 seconds of supported clock skew and one Prometheus scrape/evaluation interval. Missing or invalid leases fail closed. Keep producer and Prometheus clocks synchronized within 5 seconds; GitHub-hosted runners meet this requirement, while self-hosted runners need reliable time synchronization.

Do not tune this field by shortening Collector `metric_expiration`. The recommended five-minute exporter cleanup remains longer than the default lease and applies to all gauges, counters, and histograms. Changing Collector configuration may require reload or restart, but restart is not required for active-agent correctness.

## 3. Workflow and Agent Lineage Configuration

The `workflow:` and `agent:` config blocks control generated workflow identity, generated agent identity, and parent/child propagation. `pi.workflow.id`, `pi.agent.id`, `pi.agent.parent_id`, and related values are high-cardinality identifiers for traces/logs only; they must not be metric labels by default.

Child-identity environment-key settings use these defaults and may be customized through the same global YAML, trusted project YAML, or explicit runtime-option layers as other `agent` fields:

| Setting | Default | Purpose |
| --- | --- | --- |
| `agent.childIdentityEnvelopeVersionEnv` | `OBSERVME_CHILD_IDENTITY_ENVELOPE_VERSION` | Version marker for one complete child-identity envelope. |
| `agent.displayNameEnv` | `OBSERVME_AGENT_DISPLAY_NAME` | Child display-name value. |
| `agent.roleEnv` | `OBSERVME_AGENT_ROLE` | Child role value. |
| `agent.capabilityEnv` | `OBSERVME_AGENT_CAPABILITY` | Child capability value; this existing key remains the capability contract. |

All configured workflow, lineage, trace-context, and child-identity environment names must be safe and case-insensitively unique. They also must not collide with fixed `traceparent` or `tracestate` names. Collision diagnostics are bounded and never render the rejected configured name.

Rules:

- Generate a random `pi.agent.id` and `pi.workflow.id` when no trusted value is supplied.
- For root agents, set `pi.agent.root_id = pi.agent.id`, `pi.workflow.root_agent_id = pi.agent.id`, and `pi.agent.depth = 0`.
- For subagents, accept parent/root/depth values only from the Pi process environment supplied by a trusted ObservMe-aware launcher or from explicit runtime overrides. Project-local `.env` values may configure ObservMe but must not establish lineage provenance.
- Require a complete validated workflow, parent agent, root agent, depth, and spawn envelope. A supplied `traceparent` must be valid W3C, optional `tracestate` must validate and requires `traceparent`, and duplicate parent trace/span metadata must match. A missing `traceparent` degrades trace continuity with a bounded fallback signal without invalidating the lineage envelope.
- Reject partial, malformed, oversized, or stale propagation fail-open: generate root/orphan identity, emit only bounded sanitized failure telemetry, and never include rejected raw environment values.
- Continue a valid W3C parent context explicitly on the child `pi.session` span. If trusted lineage has no usable continuation, start a new trace and add a validated parent span link when metadata exists, otherwise emit the documented propagation-failure log/counter fallback.
- When `propagateTraceContext` is true, propagate W3C `traceparent`/`tracestate` to child processes launched by ObservMe-aware subagent wrappers.
- When `workflow.enabled` is true, propagate `OBSERVME_WORKFLOW_ID` to child processes and report depth/fan-out/orphan metrics.
- `writeCorrelationEntry` defaults to `false`. When enabled, successful startup appends at most one versioned `observme.correlation` `custom` entry to the active branch. Reload/resume/fork recovery scans only `ctx.sessionManager.getBranch()`, accepts the latest valid bounded lineage entry, ignores corrupt or abandoned-branch data, and does not duplicate an unchanged entry. ObservMe never uses `custom_message`, so this state cannot enter LLM context.
- Historical telemetry is never replayed automatically. Use the explicit, confirmed `/obs backfill` command. The removed `replayOnStart` YAML field is rejected as unknown, and `OBSERVME_REPLAY_ON_START` is no longer recognized; neither can emit a synthetic duplicate startup record.

## 4. Config Precedence

1. Defaults
2. Global config (`~/.pi/agent/observme.yaml`)
3. Project config (`<cwd>/<CONFIG_DIR_NAME>/observme.yaml`, normally `.pi/observme.yaml`) only when `ctx.isProjectTrusted()` is true
4. Project env file (`<cwd>/.env`) only when `ctx.isProjectTrusted()` is true
5. System environment variables
6. Explicit runtime options

Copy `.env.example` to `.env` for project-local extension variables, or export the same `OBSERVME_*` names in the shell before starting Pi. System environment variables override `.env` values, and `.env` must never be committed. If redacted content capture is enabled, set `OBSERVME_HASH_SALT` in the shell or trusted project `.env`; missing salts make capture fail closed.

### 4.1 Project-Local Path and Symlink Policy

Project config, project `.env`, and starter-config creation use the same fail-closed filesystem policy:

- Pi must mark the project trusted before ObservMe reads or creates a project-local file.
- The lexical path and resolved canonical target must both remain inside one stable canonical project root.
- Existing file or directory symlinks are supported when their targets remain inside that root. Out-of-root, dangling, or unverifiable symlinks are rejected.
- Reads and creates use opened file handles whose file identity, root identity, and canonical containment are verified before bytes are consumed or written. Concurrent replacement of a file, ancestor, or project root therefore rejects the operation instead of following the substituted target.
- Global and project `observme.yaml` files are limited to 262,144 bytes (256 KiB), and project `.env` is limited to 131,072 bytes (128 KiB). Exact-limit files load; larger and sparse files are rejected from opened-file metadata before content allocation or parsing.
- Normal missing in-root paths remain supported. Starter creation is exclusive and idempotent, never overwrites an existing file, and removes an unverified empty target if post-open validation fails.
- Unsafe paths use `config_source_rejected`, while oversized files use `config_source_too_large`. Warnings and status diagnostics do not include canonical targets, external paths, file contents, or environment values; the rejected layer contributes no values and other accepted layers continue to apply. Diagnostics name rejected sources separately from the accepted effective source and report a safe fallback only when whole-config validation actually replaced the merged result.

Automatic project starter file:

- On `session_start`, the extension creates `<cwd>/<CONFIG_DIR_NAME>/observme.yaml` (`<cwd>/.pi/observme.yaml` in the standard distribution) when the project is trusted and the file is missing.
- The target is resolved as an absolute contained project path before mutation. The complete existence-check/create/write window uses Pi's per-file mutation queue, so concurrent starts create at most one starter.
- Existing project config is never overwritten.
- Every generated setting is commented out. The untouched guide contributes no project layer, so active global disablement, endpoints, environment, and transport policy remain effective across startup and reload.
- To adopt project overrides, uncomment `observme:`, the required parent sections, and only the intended settings: `otlp.endpoint` / signal-specific endpoints for the Collector, `resource.attributes` for service/project/tenant/environment labels, `capture` and `privacy` for content capture and redaction, and `query.grafana` / `query.links.traceUrlTemplate` for `/obs` query commands.
- The suggested profile is privacy-preserving when adopted; raw content capture is disabled, redaction is enabled, and unsafe capture is disabled.
- Keep credentials out of YAML. Reference environment variables in YAML and set secrets through the shell or a trusted project `.env`.
- Use `~/.pi/agent/observme.yaml` for standard-distribution global defaults that should apply across projects. Only explicitly uncommented project settings override that global layer after trust.

## 5. Safe Production Defaults

```yaml
capture:
  prompts: false
  responses: false
  thinking: false
  toolArguments: false
  toolResults: false
  bashCommands: false
  bashOutput: false
  filePaths: false  # reserved; no direct live file-path recording point
privacy:
  redactionEnabled: true
  allowUnsafeCapture: false
  allowInsecureTransport: false
agent:
  propagateTraceContext: true
  propagateToSubagents: true
  writeCorrelationEntry: false
workflow:
  enabled: true
metrics:
  exportIntervalMillis: 15000
  activeAgentLeaseDurationMillis: 60000
```

## 6. Development Defaults

```yaml
otlp:
  endpoint: http://localhost:4318
  protocol: http/protobuf
privacy:
  redactionEnabled: true
  allowInsecureTransport: true  # development localhost only
capture:
  prompts: false
  responses: false
```

## 7. Local Grafana Query Development

For the bundled `observability-stack/`, the supported `/obs` command path is authenticated Grafana through Nginx at `http://localhost`. The default stack does not publish Grafana directly on `localhost:3000`; Nginx on host port 80 is the supported entrypoint.

```yaml
query:
  enabled: true
  links:
    traceUrlTemplate: http://localhost/explore?left=...
  grafana:
    url: http://localhost
    token: ${OBSERVME_GRAFANA_TOKEN}      # preferred service-account/bearer token
    username: admin                       # local Basic auth fallback
    password: ${OBSERVME_GRAFANA_PASSWORD}
    datasourceUids:
      tempo: tempo
      loki: loki
      prometheus: prometheus
    tls:
      insecureSkipVerify: false           # no TLS on the bundled local endpoint
    transport:
      preferIPv4: false                   # localhost needs no forced address family
```

Rules:

- Create a Grafana service-account token with Viewer access for read-only datasource queries and export it as `OBSERVME_GRAFANA_TOKEN`, or export/set `OBSERVME_GRAFANA_PASSWORD` from `observability-stack/secrets/grafana_admin_password` for local Basic auth. These values may come from system environment variables or a trusted project `.env` copied from `.env.example`.
- Browser login cookies are not used by the extension; `/obs` commands call the Grafana API directly from the Pi process.
- A resolved `query.grafana.token` is sent as `Authorization: Bearer ...` and takes precedence.
- When the token is blank or an unresolved placeholder, a resolved `query.grafana.username` and `query.grafana.password` are sent as Basic auth for local development.
- Query-backed commands and `/obs health` must fail fast before Grafana calls when Grafana auth is unresolved/missing/incomplete, `query.grafana.url` is invalid, or a required datasource UID is blank.
- `/obs health` must report Grafana `401`/`403` responses as authentication failures and must not print token or password values.
- The bundled endpoint is plain local HTTP, so `query.grafana.tls.insecureSkipVerify=false`; `privacy.allowInsecureTransport=true` acknowledges the development-only plaintext transport.
- `query.grafana.tls.insecureSkipVerify=true` is only for a custom self-signed HTTPS profile; production requires `privacy.allowInsecureTransport=true` as an explicit acknowledgement and should trust the CA instead.
- `transport.preferIPv4=true` uses Node's local HTTP(S) transport with IPv4 DNS lookup when a custom hostname needs it; the bundled localhost profile leaves it false.
- Provisioned datasource UIDs are `tempo`, `loki`, and `prometheus`; Loki selectors use normalized labels such as `service_name`, `pi_session_id`, `event_name`, and `event_category` for ObservMe data.

## 8. Unsafe Debug Mode

Unsafe mode must require explicit opt-in:

```yaml
privacy:
  allowUnsafeCapture: true
capture:
  prompts: true
  responses: true
  thinking: true
  toolArguments: true
  toolResults: true
```

To show redacted LLM chat content in Grafana Tempo and Loki, set `OBSERVME_CAPTURE_PROMPTS=true`, `OBSERVME_CAPTURE_RESPONSES=true`, `OBSERVME_CAPTURE_THINKING=true`, keep `OBSERVME_REDACTION_ENABLED=true`, and set `OBSERVME_HASH_SALT` before the conversation occurs. To show failed-tool output in the Tools dashboard, set `OBSERVME_CAPTURE_TOOL_RESULTS=true` with the same redaction and hash-salt safeguards; successful tool results remain span-only while failed output is emitted to the dedicated `tool.error.captured` Loki stream. Set `OBSERVME_ALLOW_UNSAFE_CAPTURE=true` only when redaction is disabled for intentionally raw local debugging. Dashboards show only new events emitted after these settings and the updated Collector are active; older data dropped by the Collector cannot be recovered.

ObservMe must display a warning when unsafe capture is active. Redacted capture must pass all configured redactors, and raw unsafe capture is permitted only when redaction is explicitly disabled with a separately validated exception.

## 9. Validation Rules

Reject config when:

- `allowUnsafeCapture=false` and `redactionEnabled=false` while any content capture is true.
- A production OTLP or Grafana endpoint uses `http://` and `privacy.allowInsecureTransport` is not true.
- `query.grafana.url` contains an embedded username or password instead of using the dedicated token or username/password settings.
- Production sets `otlp.tls.insecureSkipVerify=true` or `query.grafana.tls.insecureSkipVerify=true` and `privacy.allowInsecureTransport` is not true.
- `otlp.protocol=http/protobuf` but a signal-specific exporter URL omits the required `/v1/traces`, `/v1/metrics`, or `/v1/logs` path.
- `metrics.activeAgentLeaseDurationMillis` is fractional, non-numeric, below `10000`, above `300000`, or less than `(2 * metrics.exportIntervalMillis) + 5000`.
- Metric labels include high-cardinality fields such as workflow IDs, session IDs, agent IDs, parent/child agent IDs, trace IDs, span IDs, entry IDs, spawn IDs, or spawn tool-call IDs. The generated `observme.instance.id` / `service.instance.id` remains a resource identity used by the Collector for the `observme_instance_id` lease join; it must not be configured as an execution-derived label.
- Project-local config is read while `ctx.isProjectTrusted()` is false.
- A project config, project `.env`, or starter-config path escapes the canonical project root, changes identity during I/O, or cannot be verified safely.
- Propagated workflow or agent lineage values are malformed, too long, or contain unsafe characters.
- Configured workflow, lineage, trace-context, or child-identity environment names are malformed, duplicate another propagation key case-insensitively, or collide with `traceparent` / `tracestate`.
- Queue sizes exceed configured memory guardrails.

## 10. Minimal Config

```yaml
observme:
  otlp:
    endpoint: http://localhost:4318
```

Everything else uses safe defaults.
