@senad-d/observme 0.1.3 → 0.1.5

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/.env.example +4 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +36 -11
  4. package/dashboards/observme-agent-node-graphs.json +5 -5
  5. package/dashboards/observme-agents.json +169 -4
  6. package/dashboards/observme-alerts.yaml +16 -3
  7. package/dashboards/observme-overview.json +6 -6
  8. package/dashboards/observme-trace-journey.json +4 -4
  9. package/docs/agent-subagent-observability-requirements.md +19 -10
  10. package/docs/compatibility-matrix.md +21 -4
  11. package/docs/configuration.md +7 -2
  12. package/docs/extension-integration.md +20 -13
  13. package/docs/reference/03-pi-event-and-session-model.md +6 -6
  14. package/docs/reference/04-telemetry-semantic-conventions.md +39 -1
  15. package/docs/reference/05-otel-pipeline-and-collector.md +23 -10
  16. package/docs/reference/06-security-privacy-redaction.md +13 -6
  17. package/docs/reference/08-query-grafana-integration.md +30 -7
  18. package/docs/reference/09-dashboards-alerts-slos.md +132 -3
  19. package/docs/reference/10-testing-release-operations.md +44 -14
  20. package/docs/reference/11-deployment-runbooks.md +82 -5
  21. package/docs/reference/12-configuration-reference.md +48 -5
  22. package/docs/review-validation.md +17 -0
  23. package/examples/README.md +7 -2
  24. package/examples/collector.yaml +8 -8
  25. package/examples/integrations/subagent-runner.ts +8 -5
  26. package/examples/observme.yaml +3 -2
  27. package/package.json +14 -4
  28. package/src/commands/obs-agents.ts +17 -12
  29. package/src/commands/obs-backfill.ts +2 -2
  30. package/src/commands/obs-command-support.ts +2 -1
  31. package/src/commands/obs-cost.ts +15 -7
  32. package/src/commands/obs-health.ts +22 -2
  33. package/src/commands/obs-loki-summary.ts +4 -8
  34. package/src/commands/obs-session.ts +35 -28
  35. package/src/commands/obs-status.ts +56 -13
  36. package/src/commands/obs-tools.ts +19 -8
  37. package/src/commands/obs-trace.ts +9 -0
  38. package/src/commands/obs.ts +6 -1
  39. package/src/config/bootstrap-project-config.ts +50 -32
  40. package/src/config/defaults.ts +1 -2
  41. package/src/config/load-config.ts +270 -81
  42. package/src/config/project-paths.ts +318 -6
  43. package/src/config/query-limits.ts +10 -0
  44. package/src/config/schema.ts +18 -8
  45. package/src/config/transport-security.ts +107 -0
  46. package/src/config/validate.ts +88 -12
  47. package/src/extension.ts +2 -12
  48. package/src/integration.ts +6 -2
  49. package/src/otel/logs.ts +24 -13
  50. package/src/otel/metrics.ts +24 -13
  51. package/src/otel/otlp-endpoint.ts +41 -2
  52. package/src/otel/otlp-http-options.ts +11 -0
  53. package/src/otel/sdk.ts +155 -9
  54. package/src/otel/shutdown.ts +39 -11
  55. package/src/otel/traces.ts +34 -16
  56. package/src/pi/active-agent-lease.ts +101 -0
  57. package/src/pi/agent-tree-tracker.ts +14 -1
  58. package/src/pi/compatibility.ts +121 -0
  59. package/src/pi/event-handlers/agent-turn.ts +16 -9
  60. package/src/pi/event-handlers/lifecycle.ts +312 -106
  61. package/src/pi/event-handlers/llm.ts +17 -9
  62. package/src/pi/event-handlers/session-events.ts +53 -19
  63. package/src/pi/event-handlers/tool-bash.ts +21 -27
  64. package/src/pi/handler-internals.ts +16 -26
  65. package/src/pi/handler-runtime.ts +159 -54
  66. package/src/pi/handler-types.ts +40 -17
  67. package/src/pi/handlers.ts +12 -1
  68. package/src/pi/integration-api.ts +27 -15
  69. package/src/pi/session-correlation.ts +167 -0
  70. package/src/pi/subagent-spawn.ts +164 -31
  71. package/src/privacy/redact.ts +574 -68
  72. package/src/privacy/secret-patterns.ts +6 -1
  73. package/src/query/grafana-readiness.ts +18 -2
  74. package/src/query/grafana-transport.ts +150 -27
  75. package/src/query/grafana-url.ts +36 -0
  76. package/src/query/grafana.ts +4 -136
  77. package/src/query/loki.ts +2 -4
  78. package/src/query/prometheus.ts +8 -10
  79. package/src/query/tempo.ts +2 -4
  80. package/src/query/trace-link.ts +186 -0
  81. package/src/safety/display-bounds.ts +84 -0
  82. package/src/semconv/metrics.ts +7 -0
  83. package/src/semconv/values.ts +2 -0
package/.env.example CHANGED
@@ -13,6 +13,10 @@ OBSERVME_TENANT=local-dev
13
13
  OBSERVME_OTLP_ENDPOINT=http://localhost:4318
14
14
  OBSERVME_OTLP_PROTOCOL=http/protobuf
15
15
 
16
+ # Active-agent lease duration. Must be 10000-300000 ms and at least
17
+ # twice the metric export interval plus 5000 ms of clock-skew margin.
18
+ OBSERVME_ACTIVE_AGENT_LEASE_DURATION_MS=60000
19
+
16
20
  # Optional OTLP bearer token for remote/secured collectors. Leave blank for local dev.
17
21
  OBSERVME_OTLP_TOKEN=
18
22
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  ### Added
6
6
 
7
+ - Added a bounded GitHub-hosted Linux CI job for active-agent lease contracts and cancellation-oriented Docker integration coverage, with resource-labeled unconditional cleanup.
8
+ - Added Docker-backed active-agent lease integration coverage for clean shutdown, `SIGTERM`, and `SIGKILL`, proving cached raw claims outlive lease-aware activity without a Collector restart.
9
+ - Added a session-scoped active-agent lease controller that renews from the SDK metric collection cycle with an injectable wall clock and deterministic disposal.
10
+ - Added the `observme_agent_lease_expires_unixtime_seconds` asynchronous gauge contract with unit metadata and deterministic observable callback test support.
11
+ - Added bounded `metrics.activeAgentLeaseDurationMillis` configuration with layered `OBSERVME_ACTIVE_AGENT_LEASE_DURATION_MS` loading, fail-safe validation, and generated/example configuration coverage.
12
+ - Added a production-readiness task plan for lease-based active-agent accounting that remains accurate after crashes, forced termination, and cancelled GitHub Actions jobs.
7
13
  - Added a packaged `observme-docs` Pi skill that routes natural-language ObservMe questions to focused user, operator, reference, example, and contributor documentation through Pi's normal skill discovery, without an ObservMe system-prompt hook.
8
14
  - Added a versioned `@senad-d/observme/integration` event-bus API and transport-agnostic child-runner example for parent-side spawn/wait/join telemetry and child process lineage propagation.
9
15
  - Added README tables cataloging available metrics, trace spans, and structured log events, including opt-in and reserved signals.
@@ -15,6 +21,15 @@
15
21
 
16
22
  ### Changed
17
23
 
24
+ - Made the 0.1.5 hardening patch self-contained with explicit review units, all imported production modules included, and unrelated specification history preserved.
25
+ - Established a typed Pi event registration contract, removed non-event legacy registrations, pinned the release-resolved Pi API, and added minimum/release compatibility validation with pre-registration diagnostics.
26
+ - Centralized embedded-credential Grafana URL diagnostics so configuration validation, readiness checks, URL construction, and transports share one safe failure class and operator guidance.
27
+ - Documented credential-free Grafana base URLs and the fail-closed canonical project-file boundary across configuration, security, query, and troubleshooting guidance.
28
+ - Completed production active-agent lease documentation, raw-query migration guidance, GitHub Actions/self-hosted clock and cleanup runbooks, missing/expired-lease troubleshooting, Collector restart semantics, and sanitized release-validation evidence.
29
+ - Reframed the Collector's five-minute Prometheus `metric_expiration` as exporter-wide stale-series/cardinality cleanup, longer than the default active-agent lease and independent of leased liveness.
30
+ - Migrated active-agent dashboard totals, bounded breakdowns, aggregate topology inputs, and stuck-high alerts to leased activity, with raw/expired-claim diagnostics and a deployment-tunable stale-claim alert.
31
+ - Defined and enforced canonical lease-aware active-agent PromQL for totals, bounded breakdowns, topology inputs, and alerts, including replica deduplication, future-lease rejection, and zero-safe idle states.
32
+ - Documented the production active-agent lease, clock, convergence, instance-join, failure-mode, and backward-compatibility contract used by upcoming runtime and dashboard integration.
18
33
  - Moved the detailed technical reference into `docs/reference/` and updated package, documentation, examples, dashboards, tests, and skill routes to use the new location.
19
34
  - Made the packaged `observme-docs` skill resolve routed references from its installed package root instead of the caller's working directory or a repository checkout.
20
35
  - Reorganized documentation around `docs/README.md`, a categorized technical-reference index, and an example guide with explicit usage and safety notes.
@@ -27,6 +42,39 @@
27
42
 
28
43
  ### Fixed
29
44
 
45
+ - Simplified Pi SemVer validation into focused numeric-core and metadata checks, and made missing project-path component reversal explicit.
46
+ - Accepted supported stable Pi versions with SemVer build metadata while preserving bounded diagnostics and prerelease rejection.
47
+ - Rejected prerelease Pi builds excluded by the declared stable compatibility range before extension registration.
48
+ - Applied one control-safe 64-row/8,192-character policy to every `/obs` notification and capped query result counts at configuration and runtime boundaries.
49
+ - Coupled canonical project config, `.env`, and starter-config I/O to identity-verified file handles so concurrent symlink and ancestor swaps fail closed without exposing external paths.
50
+ - Rejected credentials embedded in Grafana base URLs during config validation, query readiness, and transport preflight without exposing their values.
51
+ - Rejected active and retained child-agent identifier collisions before creating integration spans, tree state, metrics, or propagation envelopes.
52
+ - Retained ownership of timed-out OpenTelemetry shutdowns, observed late settlement safely, and deferred session replacement until prior exporter cleanup completes.
53
+ - Replaced custom-redaction regex heuristics with bounded structural validation that rejects nested, ambiguous-alternative, and overlapping sequential repetition while preserving safe disjoint quantified alternatives.
54
+ - Used concise word-character syntax for single-brace unresolved trace-link placeholders.
55
+ - Used concise word-character syntax for dollar-brace unresolved trace-link placeholders.
56
+ - Used concise word-character syntax for double-brace unresolved trace-link placeholders.
57
+ - Split unresolved trace-link placeholder validation into simple per-syntax patterns while preserving rejection behavior.
58
+ - Documented why best-effort Grafana stream cancellation failures are intentionally ignored.
59
+ - Removed redundant explicit `undefined` support from the optional lifecycle model context property.
60
+ - Simplified lifecycle recovery header selection by replacing a nested conditional expression with explicit branches.
61
+ - Removed redundant awaiting of Pi's synchronous lifecycle status update.
62
+ - Replaced deprecated proxy tracer construction with an isolated always-off provider for disabled and shutdown trace states.
63
+ - Validated OTLP endpoints as secret-free absolute HTTP(S) URLs and constructed signal exporter paths with deterministic URL pathname semantics.
64
+ - Implemented opt-in, versioned, active-branch correlation persistence with bounded validation and idempotent reload recovery, and removed unsupported automatic replay configuration and synthetic duplicate startup telemetry.
65
+ - Bound live telemetry, query commands, and backfill correlation to Pi's typed session manager, preserved identity across reload, adopted replacement-session identity, and refreshed active metadata on session rename.
66
+ - Bounded and sanitized backend-derived `/obs cost`, `/obs tools`, and `/obs agents` labels, rows, and notification output with visible Unicode-safe truncation.
67
+ - Unified `/obs session`, `/obs trace`, and `/obs link` on one validated trace-link builder with canonical placeholders, structured Grafana fallback URLs, and bounded configuration diagnostics.
68
+ - Made multi-signal OpenTelemetry startup transactional with bounded rollback, a terminal failed controller state, sanitized Pi diagnostics, and clean later-session recovery.
69
+ - Preserved malformed environment and file configuration as bounded rejection diagnostics, with source-specific `/obs status` reporting and strict trusted `.env` parsing.
70
+ - Enforced coherent terminal subagent transitions across the integration API, agent tree, spans, events, metrics, runtime state, waits, and joins.
71
+ - Enforced production acknowledgement for Grafana and OTLP certificate-verification bypasses, wired retained OTLP TLS behavior into every exporter, and exposed effective transport security in `/obs status` and `/obs health`.
72
+ - Kept live-session and backfill OpenTelemetry providers scoped to ObservMe instead of replacing process-global providers.
73
+ - Redacted complete supported PEM private-key blocks, including malformed/truncated input, across live and backfilled content capture.
74
+ - Bounded all Grafana health and datasource response bodies before JSON parsing across default, custom Node, and injected fetch transports.
75
+ - Enforced top-level ObservMe disablement across lifecycle startup, runtime state, integration availability, and all OpenTelemetry signal factories.
76
+ - Restored clean npm dependency resolution by pinning TypeScript to the latest release supported by `typescript-eslint`.
77
+ - Integrated active-agent lease activation and deactivation with clean shutdown, duplicate/reload replacement, resume, and failed-start cleanup so final flushes cannot renew stale activity.
30
78
  - Fixed session, workflow, agent, turn, LLM, tool, Bash, histogram, active-span, failure/recovery, and session-count telemetry accuracy.
31
79
  - Fixed lifecycle serialization, duplicate session replacement, bounded shutdown/flush behavior, backfill cancellation, parallel tool correlation, and bounded agent-tree state cleanup.
32
80
  - Hardened configuration validation, project-root path confinement, endpoint security, custom redaction patterns, tenant-salted hashing, and environment propagation.
package/README.md CHANGED
@@ -62,7 +62,7 @@ This checkout implements the ObservMe MVP scope described by the packaged [`docs
62
62
 
63
63
  - Extension load checks and `/obs health` backend checks.
64
64
  - Session, workflow, agent-run, turn, LLM, tool, bash, subagent-spawn, wait/join, compaction, branch, model-change, and thinking-level telemetry.
65
- - Multi-agent tree metrics for active agents, depth, fan-out, orphan agents, trace-context propagation failures, child failures, and workflow duration.
65
+ - Lease-qualified multi-agent activity plus tree metrics for depth, fan-out, orphan agents, trace-context propagation failures, child failures, and workflow duration; abrupt exits converge without a Collector restart.
66
66
  - Session-scoped OTLP trace, metric, and log exporters with bounded queues, timeouts, and shutdown flushing.
67
67
  - Configurable capture controls, redaction, path scrubbing, hashing, truncation, and high-cardinality metric-label guards.
68
68
  - Grafana dashboard JSON, alert rules, SLO definitions, Collector examples, and compatibility matrix artifacts.
@@ -162,12 +162,28 @@ const started = observme?.startSubagent({
162
162
  });
163
163
 
164
164
  if (started?.ok) {
165
- await launchChildPi({ env: started.env });
166
- observme.completeSubagent(started.spawnId, { childStatus: "active" });
165
+ let child;
166
+ try {
167
+ child = await launchChildPi({ env: started.env });
168
+ } catch (error) {
169
+ observme.failSubagent(started.spawnId, {
170
+ childAgentId: started.childAgentId,
171
+ errorClass: error instanceof Error ? error.name : "launcher_error",
172
+ });
173
+ }
174
+
175
+ if (child) {
176
+ const result = await waitForChildPi(child);
177
+ observme.completeSubagent(started.spawnId, {
178
+ childAgentId: started.childAgentId,
179
+ childStatus: result.status,
180
+ outcome: result.status,
181
+ });
182
+ }
167
183
  }
168
184
  ```
169
185
 
170
- The discovery helper fails open when the event bus or a provider is malformed. API methods also reject unsafe/oversized requests and duplicate active lifecycle IDs without replacing existing telemetry state. Handle every discriminated failure result locally and keep orchestration functional.
186
+ The discovery helper fails open when the event bus or a provider is malformed. API methods also reject unsafe/oversized requests, duplicate active lifecycle IDs, and active or retained child-placeholder collisions without replacing existing telemetry state. Handle every discriminated failure result locally and keep orchestration functional.
171
187
 
172
188
  Use [`docs/extension-integration.md`](docs/extension-integration.md) for the complete lifecycle, validation limits, and failure contract. The shipped [`examples/integrations/subagent-runner.ts`](examples/integrations/subagent-runner.ts) wraps a generic child transport, while [`docs/agent-subagent-observability-requirements.md`](docs/agent-subagent-observability-requirements.md) covers the larger orchestration design.
173
189
 
@@ -230,10 +246,17 @@ Metric labels are intentionally low-cardinality. Session, workflow, agent, trace
230
246
  | Histogram | Workflow and agent tree | `observme_workflow_duration_ms`<br>`observme_agent_run_duration_ms`<br>`observme_subagent_spawn_duration_ms`<br>`observme_agent_wait_duration_ms`<br>`observme_agent_join_duration_ms`<br>`observme_agent_tree_depth`<br>`observme_agent_tree_width`<br>`observme_agent_fanout_count` | Workflow/agent latency and multi-agent tree shape. |
231
247
  | Histogram | Operation latency | `observme_turn_duration_ms`<br>`observme_llm_request_duration_ms`<br>`observme_tool_duration_ms`<br>`observme_bash_duration_ms`<br>`observme_handler_duration_ms` | End-to-end operation and extension-handler duration in milliseconds. |
232
248
  | Histogram | Context and payload size | `observme_compaction_tokens_before`<br>`observme_prompt_size_chars`<br>`observme_response_size_chars`<br>`observme_tool_result_size_chars` | Context size before compaction and payload sizes in characters. Size metrics do not contain payload content. |
233
- | Up-down counter | Current activity | `observme_active_spans`<br>`observme_active_agents` | Active operations and agents for the current exported stream. |
249
+ | Up-down counter | Current activity | `observme_active_spans`<br>`observme_active_agents` | Active operations and the compatibility lifecycle claim for agents in the current exported stream. |
250
+ | Asynchronous gauge | Agent liveness lease | `observme_agent_lease_expires_unixtime_seconds` | Absolute Unix expiry renewed on each metric collection while the session is active; operational active-agent queries join it to the positive lifecycle claim. |
234
251
  | Registered, not yet recorded | Agent lifetime | `observme_agent_lifetime_duration_ms` | Reserved instrument; the current live handlers do not record measurements yet. |
235
252
  | Registered, not yet recorded | Official GenAI compatibility | `gen_ai.client.token.usage`<br>`gen_ai.client.operation.duration` | Reserved OpenTelemetry GenAI instruments; use the `observme_llm_*` metrics above for current data. |
236
253
 
254
+ ### Active-agent liveness
255
+
256
+ `observme_active_agents` remains a clean-start/clean-shutdown lifecycle claim, but raw sums are not authoritative after a crash, `SIGKILL`, cancelled GitHub Actions job, or lost runner. The shipped dashboards and alerts count an instance only when that claim is positive and its `observme_agent_lease_expires_unixtime_seconds` value is current and within the supported future horizon. Clean shutdown reaches zero after export/scrape propagation; an ungraceful exit converges within the configured lease plus up to 5 seconds of supported clock skew and one Prometheus scrape/evaluation interval (80 seconds with the default 60-second lease and a 15-second scrape).
257
+
258
+ Missing, expired, malformed, or pathologically future leases fail closed. GitHub-hosted runners satisfy the clock requirement; self-hosted runners must keep the producer and Prometheus clocks synchronized within 5 seconds. Collector restart is not required for convergence, even when its Prometheus exporter continues to expose a cached raw claim. Migrate custom panels, alerts, and recording rules from raw `sum(observme_active_agents)` queries to the [canonical lease-aware PromQL](docs/reference/09-dashboards-alerts-slos.md#131-canonical-active-agent-promql), and use raw/expired claims only for diagnostics.
259
+
237
260
  ### Traces
238
261
 
239
262
  | Span name | Created for | Notes |
@@ -261,7 +284,7 @@ All operational logs use short event bodies plus structured attributes. Correlat
261
284
  | Configuration, session, and workflow | `config.rejected`<br>`session.started`<br>`session.shutdown`<br>`session.duplicate_start`<br>`workflow.started`<br>`workflow.completed`<br>`workflow.failed` | Configuration diagnostics and top-level lifecycle/outcome events. |
262
285
  | Reserved session events | `session.named`<br>`session.error` | Public event names reserved for compatibility; current live handlers do not emit them. |
263
286
  | Agent run | `agent.run.started`<br>`agent.run.completed`<br>`agent.run.failed` | Agent-run lifecycle and outcome. |
264
- | Subagent lineage | `agent.spawn.started`<br>`agent.spawn.completed`<br>`agent.spawn.failed`<br>`agent.wait.started`<br>`agent.wait.completed`<br>`agent.join.started`<br>`agent.join.completed`<br>`agent.orphaned`<br>`trace_context.propagation_failed` | Parent/child lifecycle, lineage gaps, propagation failures, and join outcomes. |
287
+ | Subagent lineage | `agent.spawn.started`<br>`agent.spawn.completed`<br>`agent.spawn.failed`<br>`agent.spawn.cancelled`<br>`agent.wait.started`<br>`agent.wait.completed`<br>`agent.join.started`<br>`agent.join.completed`<br>`agent.orphaned`<br>`trace_context.propagation_failed` | Parent/child lifecycle, lineage gaps, propagation failures, and join outcomes. |
265
288
  | Turn | `turn.started`<br>`turn.completed` | Turn lifecycle with run/turn correlation. |
266
289
  | LLM lifecycle | `llm.request.started`<br>`llm.request.completed`<br>`llm.request.failed` | Content-free provider request lifecycle, usage, cost, stop reason, and bounded errors. |
267
290
  | LLM content | `llm.prompt.captured`<br>`llm.response.captured`<br>`llm.thinking.captured` | Emitted only when the corresponding prompt, response, or thinking capture flag is enabled and redaction succeeds. |
@@ -280,7 +303,7 @@ ObservMe supports layered configuration with this precedence:
280
303
  defaults → global ~/.pi/agent/observme.yaml → trusted project config → trusted project .env → system environment variables → runtime options
281
304
  ```
282
305
 
283
- Factory-safe loading uses defaults/global/system-environment/runtime options only. Session-scoped loading can add trusted project config and a project-local `.env` when Pi marks the project trusted. `/obs status` reports the effective config source, whether project-local config was loaded, skipped because the project is untrusted, or missing, plus bounded rejection issue codes, the configured Grafana URL, and query-readiness status without rendering tokens or passwords. In untrusted projects, ObservMe does not read project-local config or `.env` files and uses safe defaults/global/system-environment layers instead. Invalid or unsafe configuration emits a bounded `config.rejected` diagnostic and falls back safely without exposing rejected values.
306
+ Factory-safe loading uses defaults/global/system-environment/runtime options only. Session-scoped loading can add trusted project config and a project-local `.env` when Pi marks the project trusted. These project files must remain inside the stable canonical project root: in-root symlinks are supported, while out-of-root, dangling, replaced, or unverifiable paths fail closed through identity-verified file I/O. `/obs status` reports the effective config source, whether project-local config was loaded, skipped because the project is untrusted, missing, or rejected, plus bounded rejection issue codes, effective OTLP/Grafana transport security, the configured Grafana URL, and query-readiness status without rendering tokens, passwords, canonical targets, or external paths. In untrusted projects, ObservMe does not read project-local config or `.env` files and uses safe defaults/global/system-environment layers instead. Invalid or unsafe configuration emits a bounded `config.rejected` diagnostic and falls back safely without exposing rejected values.
284
307
 
285
308
  Cross-process agent lineage has a separate boundary: only the Pi process environment available to the shipped extension, or explicit runtime options for controlled embedders, is eligible for parent provenance. Project-local `.env` values configure ObservMe but cannot establish lineage. A child accepts only a complete validated workflow/parent/root/depth/spawn envelope and valid W3C context; malformed or stale envelopes fail open to a root/orphan fallback with sanitized telemetry.
286
309
 
@@ -310,7 +333,7 @@ To show failed-tool output such as GuardMe denial messages in the Tools dashboar
310
333
 
311
334
  Metadata such as token counts, duration, status, model/provider, tool name, and agent role/depth is captured by default. High-cardinality identifiers (session IDs, workflow IDs, agent IDs, trace/span IDs, entry IDs) are allowed on spans/logs for drill-down but are blocked from Prometheus metric labels.
312
335
 
313
- Grafana-backed query commands use the Grafana HTTP API, so browser login cookies are irrelevant. Configure either a Grafana service-account token (`OBSERVME_GRAFANA_TOKEN`) or local Basic auth (`OBSERVME_GRAFANA_USERNAME`/`OBSERVME_GRAFANA_PASSWORD`); token auth takes precedence and secrets are never rendered in command errors. You can supply these values as system environment variables before starting Pi, or copy the `.env.example` shipped with ObservMe to `.env` in a trusted project; system environment variables override `.env` values.
336
+ Grafana-backed query commands use the Grafana HTTP API, so browser login cookies are irrelevant. Keep `query.grafana.url` credential-free; a base URL containing a username or password is rejected before network I/O with a bounded diagnostic that does not render the URL or credentials. Configure either a Grafana service-account token (`OBSERVME_GRAFANA_TOKEN`) or local Basic auth (`OBSERVME_GRAFANA_USERNAME`/`OBSERVME_GRAFANA_PASSWORD`); token auth takes precedence and secrets are never rendered in command errors. You can supply these values as system environment variables before starting Pi, or copy the `.env.example` shipped with ObservMe to `.env` in a trusted project; system environment variables override `.env` values.
314
337
 
315
338
  ### Supported local-stack query profile
316
339
 
@@ -336,7 +359,7 @@ query:
336
359
  preferIPv4: true
337
360
  ```
338
361
 
339
- Create a Grafana service-account token in Grafana (Administration → Users and access → Service accounts → Add service account/token; Viewer is enough for read-only datasource queries) and export it as `OBSERVME_GRAFANA_TOKEN`, or for local-only Basic auth read the generated admin password from the repository-only local stack's secrets directory. If you prefer a project-local env file, run `cp .env.example .env`, fill either `OBSERVME_GRAFANA_TOKEN` or `OBSERVME_GRAFANA_PASSWORD`, then restart Pi from that project. The local Collector and Loki profile uses `service.name=observme-pi-extension`; Loki queries use normalized labels such as `service_name`, `pi_session_id`, `pi_agent_id`, `pi_agent_run_id`, `event_name`, and `event_category`. If data is visible in Grafana but `/obs` commands fail, run `/obs health` and check extension env loading, Grafana auth, datasource UIDs, TLS, and DNS details.
362
+ Create a Grafana service-account token in Grafana (Administration → Users and access → Service accounts → Add service account/token; Viewer is enough for read-only datasource queries) and export it as `OBSERVME_GRAFANA_TOKEN`, or for local-only Basic auth read the generated admin password from the repository-only local stack's secrets directory. Certificate verification stays enabled for HTTPS by default. Production rejects OTLP or Grafana certificate-verification bypass unless `privacy.allowInsecureTransport: true` explicitly acknowledges the risk; `otlp.tls.enabled` is not a supported setting because each endpoint URL scheme selects HTTP or HTTPS. If you prefer a project-local env file, run `cp .env.example .env`, fill either `OBSERVME_GRAFANA_TOKEN` or `OBSERVME_GRAFANA_PASSWORD`, then restart Pi from that project. The local Collector and Loki profile uses `service.name=observme-pi-extension`; Loki queries use normalized labels such as `service_name`, `pi_session_id`, `pi_agent_id`, `pi_agent_run_id`, `event_name`, and `event_category`. If data is visible in Grafana but `/obs` commands fail, run `/obs health` and check extension env loading, Grafana auth, datasource UIDs, TLS, and DNS details.
340
363
 
341
364
  ### Show LLM chat content in Grafana
342
365
 
@@ -361,7 +384,7 @@ Full configuration schema: `docs/reference/12-configuration-reference.md`. Full
361
384
  - ObservMe does not execute shell commands itself; it only observes tool/bash execution events emitted by Pi.
362
385
  - ObservMe never blocks Pi agent execution when the observability backend is degraded or unreachable.
363
386
  - ObservMe starts exporters only for a trusted session and shuts them down with bounded timeouts.
364
- - ObservMe does not continuously tail the full Pi session file; startup recovery reads only minimal session/correlation state, and historical replay requires an explicit `/obs backfill` command.
387
+ - ObservMe does not continuously tail the full Pi session file. Optional correlation persistence uses one bounded `observme.correlation` custom entry on the active Pi branch, never LLM context; historical replay is never automatic and requires an explicit, confirmed `/obs backfill` command.
365
388
  - Optional content capture is disabled by default and must pass through the redaction pipeline before export when enabled.
366
389
  - `/obs` query commands are read-only and are not imported by telemetry-emission code paths.
367
390
  - Invalid or unsafe configuration falls back to safe defaults with bounded `config.rejected`, `/obs status`, and available Pi UI diagnostics; rejected values are never rendered. Intentionally unsafe capture emits a visible warning.
@@ -383,7 +406,7 @@ These assets are included in the npm package:
383
406
 
384
407
  Open **ObservMe Overview** first for health/SLO chips, workload, cost, latency, agent-lineage status, and time-preserving links to focused dashboards. Use **Trace Journey** to follow a session/workflow/agent/run across Prometheus aggregates, Loki logs, and Tempo traces. Use **LLM Conversations** only for redacted opt-in content; raw prompt, response, command, path, and error-message values should never be placed in dashboard URLs or Prometheus labels. Empty failure tables normally mean no matching failures in the selected range, while optional content panels can be empty because capture is disabled by default.
385
408
 
386
- The full dashboard map, standard variables, drill-down workflow, threshold colors, and zero-state semantics are documented in `docs/reference/09-dashboards-alerts-slos.md`.
409
+ The full dashboard map, canonical lease-aware active-agent queries, raw-query migration, standard variables, drill-down workflow, threshold colors, and zero-state semantics are documented in `docs/reference/09-dashboards-alerts-slos.md`. For an unexpected zero or stale raw claim, follow the active-lease troubleshooting flow in `docs/reference/11-deployment-runbooks.md` and check Export Health before concluding that the producer stopped.
387
410
 
388
411
  ## Documentation Set
389
412
 
@@ -415,8 +438,10 @@ npm run lint:fix # ESLint auto-fix pass
415
438
  npm run format:check
416
439
  npm run test
417
440
  npm run test:integration:collector
441
+ npm run test:integration:active-agent-lease
418
442
  npm run test:integration:grafana-stack
419
443
  npm run check:pack
444
+ npm run pack:dry-run
420
445
  npm run smoke:pi-runtime
421
446
  npm run validate:grafana-obs
422
447
  pi --no-extensions -e .
@@ -45,7 +45,7 @@
45
45
  "type": "prometheus",
46
46
  "uid": "prometheus"
47
47
  },
48
- "description": "Aggregate Pi lineage topology built only from bounded Prometheus labels; it is not a single trace tree. Numeric main stats are current gauges or selected-range counts. Red nodes and edges identify spawn failures, orphan lineage, and trace-context propagation failures. During healthy idle ranges, only the Pi root may render because no aggregate activity exists; use Agents for ratios and Trace Journey for one execution.",
48
+ "description": "Aggregate fleet topology built only from bounded Prometheus labels; it is not per-workflow truth or a single trace tree. Role nodes combine canonical leased-current activity with selected-range run/spawn counts, so node and edge values must not be read as one execution. Clean shutdown removes leased activity after export/scrape propagation; after a crash, SIGKILL, cancelled job, or runner loss, leased activity converges to zero within the default 60-second lease plus one Prometheus scrape while raw claims may stay cached. Red nodes and edges identify spawn, lineage, or propagation failures. /obs agents is current-process child state; use Trace Journey for one execution.",
49
49
  "fieldConfig": {
50
50
  "defaults": {
51
51
  "mappings": [],
@@ -88,7 +88,7 @@
88
88
  "uid": "prometheus"
89
89
  },
90
90
  "editorMode": "code",
91
- "expr": "label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(vector(1), \"id\", \"pi-root\", \"__name__\", \".*\"), \"title\", \"Pi root\", \"id\", \".*\"), \"subTitle\", \"workflow entry\", \"id\", \".*\"), \"mainStat\", \"entry\", \"id\", \".*\"), \"secondaryStat\", \"aggregate\", \"id\", \".*\"), \"color\", \"blue\", \"id\", \".*\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(observme_active_agents) by (agent_role) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role) or sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role)), \"id\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"title\", \"Agent: $1\", \"agent_role\", \"(.+)\"), \"subTitle\", \"active or observed role\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"role\", \"agent_role\", \"(.+)\"), \"color\", \"green\", \"agent_role\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (subagent_depth), \"id\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"title\", \"Subagents depth $1\", \"subagent_depth\", \"(.+)\"), \"subtitle\", \"spawned in range\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"subagent_depth\", \"(.+)\"), \"secondaryStat\", \"depth\", \"subagent_depth\", \"(.+)\"), \"color\", \"orange\", \"subagent_depth\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"id\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"title\", \"Spawn failure: $1\", \"error_class\", \"(.+)\"), \"subtitle\", \"failed spawns\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"error\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_orphan_agents_total[$__range])) by (reason) > 0), \"id\", \"orphan_$1\", \"reason\", \"(.+)\"), \"title\", \"Orphan: $1\", \"reason\", \"(.+)\"), \"subtitle\", \"lineage missing\", \"reason\", \"(.+)\"), \"mainStat\", \"orphans\", \"reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_trace_context_propagation_failures_total[$__range])) by (reason) > 0), \"id\", \"propagation_$1\", \"reason\", \"(.+)\"), \"title\", \"Propagation: $1\", \"reason\", \"(.+)\"), \"subtitle\", \"trace context failures\", \"reason\", \"(.+)\"), \"mainStat\", \"failures\", \"reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\")",
91
+ "expr": "label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(vector(1), \"id\", \"pi-root\", \"__name__\", \".*\"), \"title\", \"Pi root\", \"id\", \".*\"), \"subTitle\", \"workflow entry\", \"id\", \".*\"), \"mainStat\", \"entry\", \"id\", \".*\"), \"secondaryStat\", \"aggregate\", \"id\", \".*\"), \"color\", \"blue\", \"id\", \".*\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum by (agent_role) (max by (observme_instance_id, agent_role) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role) or sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role)), \"id\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"title\", \"Agent: $1\", \"agent_role\", \"(.+)\"), \"subTitle\", \"active or observed role\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"role\", \"agent_role\", \"(.+)\"), \"color\", \"green\", \"agent_role\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (subagent_depth), \"id\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"title\", \"Subagents depth $1\", \"subagent_depth\", \"(.+)\"), \"subtitle\", \"spawned in range\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"subagent_depth\", \"(.+)\"), \"secondaryStat\", \"depth\", \"subagent_depth\", \"(.+)\"), \"color\", \"orange\", \"subagent_depth\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"id\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"title\", \"Spawn failure: $1\", \"error_class\", \"(.+)\"), \"subtitle\", \"failed spawns\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"error\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_orphan_agents_total[$__range])) by (reason) > 0), \"id\", \"orphan_$1\", \"reason\", \"(.+)\"), \"title\", \"Orphan: $1\", \"reason\", \"(.+)\"), \"subtitle\", \"lineage missing\", \"reason\", \"(.+)\"), \"mainStat\", \"orphans\", \"reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_trace_context_propagation_failures_total[$__range])) by (reason) > 0), \"id\", \"propagation_$1\", \"reason\", \"(.+)\"), \"title\", \"Propagation: $1\", \"reason\", \"(.+)\"), \"subtitle\", \"trace context failures\", \"reason\", \"(.+)\"), \"mainStat\", \"failures\", \"reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\")",
92
92
  "format": "table",
93
93
  "instant": true,
94
94
  "legendFormat": "nodes",
@@ -101,7 +101,7 @@
101
101
  "uid": "prometheus"
102
102
  },
103
103
  "editorMode": "code",
104
- "expr": "label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(observme_active_agents) by (agent_role) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role)), \"source\", \"pi-root\", \"agent_role\", \"(.+)\"), \"target\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"active/runs\", \"agent_role\", \"(.+)\"), \"color\", \"gray\", \"agent_role\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role, subagent_depth, spawn_type, spawn_reason), \"source\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"target\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"spawn_type/reason\", \"agent_role\", \"(.+)\"), \"color\", \"gray\", \"agent_role\", \"(.+)\"), \"id\", \":\", \"source\", \"target\", \"spawn_type\", \"spawn_reason\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"source\", \"pi-root\", \"error_class\", \"(.+)\"), \"target\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"spawn\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_orphan_agents_total[$__range])) by (reason) > 0), \"source\", \"pi-root\", \"reason\", \"(.+)\"), \"target\", \"orphan_$1\", \"reason\", \"(.+)\"), \"mainStat\", \"orphans\", \"reason\", \"(.+)\"), \"secondaryStat\", \"lineage\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_trace_context_propagation_failures_total[$__range])) by (reason) > 0), \"source\", \"pi-root\", \"reason\", \"(.+)\"), \"target\", \"propagation_$1\", \"reason\", \"(.+)\"), \"mainStat\", \"failures\", \"reason\", \"(.+)\"), \"secondaryStat\", \"trace context\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\"), \"id\", \":\", \"source\", \"target\")",
104
+ "expr": "label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum by (agent_role) (max by (observme_instance_id, agent_role) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role)), \"source\", \"pi-root\", \"agent_role\", \"(.+)\"), \"target\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"active/runs\", \"agent_role\", \"(.+)\"), \"color\", \"gray\", \"agent_role\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role, subagent_depth, spawn_type, spawn_reason), \"source\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"target\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"spawn_type/reason\", \"agent_role\", \"(.+)\"), \"color\", \"gray\", \"agent_role\", \"(.+)\"), \"id\", \":\", \"source\", \"target\", \"spawn_type\", \"spawn_reason\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"source\", \"pi-root\", \"error_class\", \"(.+)\"), \"target\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"spawn\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_orphan_agents_total[$__range])) by (reason) > 0), \"source\", \"pi-root\", \"reason\", \"(.+)\"), \"target\", \"orphan_$1\", \"reason\", \"(.+)\"), \"mainStat\", \"orphans\", \"reason\", \"(.+)\"), \"secondaryStat\", \"lineage\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\"), \"id\", \":\", \"source\", \"target\") or label_join(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_trace_context_propagation_failures_total[$__range])) by (reason) > 0), \"source\", \"pi-root\", \"reason\", \"(.+)\"), \"target\", \"propagation_$1\", \"reason\", \"(.+)\"), \"mainStat\", \"failures\", \"reason\", \"(.+)\"), \"secondaryStat\", \"trace context\", \"reason\", \"(.+)\"), \"color\", \"red\", \"reason\", \"(.+)\"), \"id\", \":\", \"source\", \"target\")",
105
105
  "format": "table",
106
106
  "instant": true,
107
107
  "legendFormat": "edges",
@@ -137,7 +137,7 @@
137
137
  "type": "prometheus",
138
138
  "uid": "prometheus"
139
139
  },
140
- "description": "Aggregate delegation topology by bounded agent role, spawn reason, and resulting subagent depth; it is not a single trace tree. Numeric main stats are selected-range counts. Red health nodes and edges summarize spawn failures by emitted error class because the failure counter does not carry spawn reason. An empty graph is a healthy idle state when no spawns or failures occurred; use Agents for aggregate analysis and Trace Journey for one execution.",
140
+ "description": "Aggregate fleet delegation topology by bounded role, spawn reason, and resulting depth; it is not per-workflow truth or a single trace tree. Role nodes may combine canonical leased-current activity with selected-range run/spawn counts, while other values are selected-range aggregates. Clean shutdown removes leased activity after export/scrape propagation; after an ungraceful exit, leased activity converges to zero within the default 60-second lease plus one Prometheus scrape while raw claims may stay cached. Red health nodes and edges summarize spawn failures. /obs agents is local current-process child state; use Trace Journey for one execution.",
141
141
  "fieldConfig": {
142
142
  "defaults": {
143
143
  "mappings": [],
@@ -180,7 +180,7 @@
180
180
  "uid": "prometheus"
181
181
  },
182
182
  "editorMode": "code",
183
- "expr": "label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(observme_active_agents) by (agent_role) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role) or sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role)), \"id\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"title\", \"Agent: $1\", \"agent_role\", \"(.+)\"), \"subTitle\", \"active or observed role\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"role\", \"agent_role\", \"(.+)\"), \"color\", \"green\", \"agent_role\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (spawn_reason), \"id\", \"spawn_reason_$1\", \"spawn_reason\", \"(.+)\"), \"title\", \"Spawn: $1\", \"spawn_reason\", \"(.+)\"), \"subTitle\", \"spawn reason\", \"spawn_reason\", \"(.+)\"), \"mainStat\", \"spawns\", \"spawn_reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"spawn_reason\", \"(.+)\"), \"color\", \"purple\", \"spawn_reason\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (subagent_depth), \"id\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"title\", \"Subagents depth $1\", \"subagent_depth\", \"(.+)\"), \"subTitle\", \"spawned in range\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"subagent_depth\", \"(.+)\"), \"secondaryStat\", \"depth\", \"subagent_depth\", \"(.+)\"), \"color\", \"orange\", \"subagent_depth\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) > 0), \"id\", \"spawn-health\", \"__name__\", \".*\"), \"title\", \"Spawn failure health\", \"id\", \".*\"), \"subTitle\", \"failure-only aggregate\", \"id\", \".*\"), \"mainStat\", \"failures\", \"id\", \".*\"), \"secondaryStat\", \"health\", \"id\", \".*\"), \"color\", \"red\", \"id\", \".*\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"id\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"title\", \"Spawn failure: $1\", \"error_class\", \"(.+)\"), \"subTitle\", \"failed spawns\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"error class\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\")",
183
+ "expr": "label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum by (agent_role) (max by (observme_instance_id, agent_role) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or sum(increase(observme_agent_runs_total[$__range])) by (agent_role) or sum(increase(observme_subagents_spawned_total[$__range])) by (agent_role)), \"id\", \"agent_$1\", \"agent_role\", \"(.+)\"), \"title\", \"Agent: $1\", \"agent_role\", \"(.+)\"), \"subTitle\", \"active or observed role\", \"agent_role\", \"(.+)\"), \"mainStat\", \"count\", \"agent_role\", \"(.+)\"), \"secondaryStat\", \"role\", \"agent_role\", \"(.+)\"), \"color\", \"green\", \"agent_role\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (spawn_reason), \"id\", \"spawn_reason_$1\", \"spawn_reason\", \"(.+)\"), \"title\", \"Spawn: $1\", \"spawn_reason\", \"(.+)\"), \"subTitle\", \"spawn reason\", \"spawn_reason\", \"(.+)\"), \"mainStat\", \"spawns\", \"spawn_reason\", \"(.+)\"), \"secondaryStat\", \"reason\", \"spawn_reason\", \"(.+)\"), \"color\", \"purple\", \"spawn_reason\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace(sum(increase(observme_subagents_spawned_total[$__range])) by (subagent_depth), \"id\", \"subagents_depth_$1\", \"subagent_depth\", \"(.+)\"), \"title\", \"Subagents depth $1\", \"subagent_depth\", \"(.+)\"), \"subTitle\", \"spawned in range\", \"subagent_depth\", \"(.+)\"), \"mainStat\", \"spawns\", \"subagent_depth\", \"(.+)\"), \"secondaryStat\", \"depth\", \"subagent_depth\", \"(.+)\"), \"color\", \"orange\", \"subagent_depth\", \"(.+)\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) > 0), \"id\", \"spawn-health\", \"__name__\", \".*\"), \"title\", \"Spawn failure health\", \"id\", \".*\"), \"subTitle\", \"failure-only aggregate\", \"id\", \".*\"), \"mainStat\", \"failures\", \"id\", \".*\"), \"secondaryStat\", \"health\", \"id\", \".*\"), \"color\", \"red\", \"id\", \".*\") or label_replace(label_replace(label_replace(label_replace(label_replace(label_replace((sum(increase(observme_subagent_spawn_failures_total[$__range])) by (error_class) > 0), \"id\", \"spawn_failure_$1\", \"error_class\", \"(.+)\"), \"title\", \"Spawn failure: $1\", \"error_class\", \"(.+)\"), \"subTitle\", \"failed spawns\", \"error_class\", \"(.+)\"), \"mainStat\", \"failures\", \"error_class\", \"(.+)\"), \"secondaryStat\", \"error class\", \"error_class\", \"(.+)\"), \"color\", \"red\", \"error_class\", \"(.+)\")",
184
184
  "format": "table",
185
185
  "instant": true,
186
186
  "legendFormat": "nodes",
@@ -1065,7 +1065,7 @@
1065
1065
  "type": "prometheus",
1066
1066
  "uid": "prometheus"
1067
1067
  },
1068
- "description": "Current active-agent gauge grouped by the emitted agent_role label, plus the global total. The tunable total=100 reference matches ObservMeActiveAgentsStuckHigh; a missing gauge is unknown telemetry, not healthy zero.",
1068
+ "description": "Canonical leased-active agents grouped by emitted agent_role, plus the fleet total. Clean shutdown reaches zero after export/scrape propagation; after a crash, SIGKILL, cancelled job, or runner loss, the leased total converges to zero within the default 60-second lease plus one Prometheus scrape even when raw claims stay cached. Zero can also mean healthy idle or a failed-closed lease, so compare Export Health. /obs agents reports current-process child state, not this aggregate fleet view. The total=100 reference matches the deployment-tunable ObservMeActiveAgentsStuckHigh threshold.",
1069
1069
  "fieldConfig": {
1070
1070
  "defaults": {
1071
1071
  "color": {
@@ -1115,7 +1115,7 @@
1115
1115
  "uid": "prometheus"
1116
1116
  },
1117
1117
  "editorMode": "code",
1118
- "expr": "sum(observme_active_agents) by (agent_role)",
1118
+ "expr": "sum by (agent_role) (max by (observme_instance_id, agent_role) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or on() vector(0)",
1119
1119
  "range": true,
1120
1120
  "refId": "A",
1121
1121
  "legendFormat": "{{agent_role}}"
@@ -1126,7 +1126,7 @@
1126
1126
  "uid": "prometheus"
1127
1127
  },
1128
1128
  "editorMode": "code",
1129
- "expr": "sum(observme_active_agents)",
1129
+ "expr": "sum(max by (observme_instance_id) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or vector(0)",
1130
1130
  "range": true,
1131
1131
  "refId": "B",
1132
1132
  "legendFormat": "total active agents"
@@ -1697,6 +1697,171 @@
1697
1697
  "x": 0,
1698
1698
  "y": 57
1699
1699
  },
1700
+ "id": 105,
1701
+ "panels": [],
1702
+ "title": "Lease health diagnostics",
1703
+ "type": "row"
1704
+ },
1705
+ {
1706
+ "datasource": {
1707
+ "type": "prometheus",
1708
+ "uid": "prometheus"
1709
+ },
1710
+ "description": "Diagnostic positive lifecycle claims before lease qualification. These are not live-agent totals and may remain cached after a crash, SIGKILL, cancelled job, or runner loss; leased activity converges to zero within the default 60-second lease plus one Prometheus scrape. Clean shutdown exports zero immediately. Compare Expired active claims and Export Health. /obs agents reports only current-process child state, not fleet liveness.",
1711
+ "fieldConfig": {
1712
+ "defaults": {
1713
+ "color": {
1714
+ "mode": "thresholds"
1715
+ },
1716
+ "mappings": [],
1717
+ "thresholds": {
1718
+ "mode": "absolute",
1719
+ "steps": [
1720
+ {
1721
+ "color": "green",
1722
+ "value": null
1723
+ }
1724
+ ]
1725
+ },
1726
+ "unit": "short"
1727
+ },
1728
+ "overrides": []
1729
+ },
1730
+ "gridPos": {
1731
+ "h": 8,
1732
+ "w": 12,
1733
+ "x": 0,
1734
+ "y": 58
1735
+ },
1736
+ "id": 22,
1737
+ "links": [
1738
+ {
1739
+ "targetBlank": false,
1740
+ "title": "Open Export Health",
1741
+ "url": "/d/observme-export-health/observme-export-health?${__url_time_range}"
1742
+ }
1743
+ ],
1744
+ "options": {
1745
+ "colorMode": "value",
1746
+ "graphMode": "area",
1747
+ "justifyMode": "auto",
1748
+ "orientation": "auto",
1749
+ "reduceOptions": {
1750
+ "calcs": [
1751
+ "lastNotNull"
1752
+ ],
1753
+ "fields": "",
1754
+ "values": false
1755
+ },
1756
+ "showPercentChange": false,
1757
+ "textMode": "auto",
1758
+ "wideLayout": true
1759
+ },
1760
+ "pluginVersion": "11.1.0",
1761
+ "targets": [
1762
+ {
1763
+ "datasource": {
1764
+ "type": "prometheus",
1765
+ "uid": "prometheus"
1766
+ },
1767
+ "editorMode": "code",
1768
+ "expr": "sum(max by (observme_instance_id) (observme_active_agents > 0)) or vector(0)",
1769
+ "instant": true,
1770
+ "range": false,
1771
+ "refId": "A"
1772
+ }
1773
+ ],
1774
+ "title": "Raw active claims (diagnostic)",
1775
+ "type": "stat"
1776
+ },
1777
+ {
1778
+ "datasource": {
1779
+ "type": "prometheus",
1780
+ "uid": "prometheus"
1781
+ },
1782
+ "description": "Positive raw claims whose lease has expired. They are stale diagnostics and never contribute to leased active totals; clean shutdown normally avoids them, while ungraceful exits appear after the default 60-second lease plus one Prometheus scrape and may remain until Collector cleanup. Yellow at 1 and red at 5 are deployment-tunable. Compare Raw active claims and Export Health. /obs agents reports only current-process child state, not fleet liveness.",
1783
+ "fieldConfig": {
1784
+ "defaults": {
1785
+ "color": {
1786
+ "mode": "thresholds"
1787
+ },
1788
+ "mappings": [],
1789
+ "thresholds": {
1790
+ "mode": "absolute",
1791
+ "steps": [
1792
+ {
1793
+ "color": "green",
1794
+ "value": null
1795
+ },
1796
+ {
1797
+ "color": "yellow",
1798
+ "value": 1
1799
+ },
1800
+ {
1801
+ "color": "red",
1802
+ "value": 5
1803
+ }
1804
+ ]
1805
+ },
1806
+ "unit": "short"
1807
+ },
1808
+ "overrides": []
1809
+ },
1810
+ "gridPos": {
1811
+ "h": 8,
1812
+ "w": 12,
1813
+ "x": 12,
1814
+ "y": 58
1815
+ },
1816
+ "id": 23,
1817
+ "links": [
1818
+ {
1819
+ "targetBlank": false,
1820
+ "title": "Open Export Health",
1821
+ "url": "/d/observme-export-health/observme-export-health?${__url_time_range}"
1822
+ }
1823
+ ],
1824
+ "options": {
1825
+ "colorMode": "value",
1826
+ "graphMode": "area",
1827
+ "justifyMode": "auto",
1828
+ "orientation": "auto",
1829
+ "reduceOptions": {
1830
+ "calcs": [
1831
+ "lastNotNull"
1832
+ ],
1833
+ "fields": "",
1834
+ "values": false
1835
+ },
1836
+ "showPercentChange": false,
1837
+ "textMode": "auto",
1838
+ "wideLayout": true
1839
+ },
1840
+ "pluginVersion": "11.1.0",
1841
+ "targets": [
1842
+ {
1843
+ "datasource": {
1844
+ "type": "prometheus",
1845
+ "uid": "prometheus"
1846
+ },
1847
+ "editorMode": "code",
1848
+ "expr": "sum(max by (observme_instance_id) ((observme_active_agents > 0) and on (observme_instance_id) (observme_agent_lease_expires_unixtime_seconds <= time()))) or vector(0)",
1849
+ "instant": true,
1850
+ "range": false,
1851
+ "refId": "A"
1852
+ }
1853
+ ],
1854
+ "title": "Expired active claims (diagnostic)",
1855
+ "type": "stat"
1856
+ },
1857
+ {
1858
+ "collapsed": false,
1859
+ "gridPos": {
1860
+ "h": 1,
1861
+ "w": 24,
1862
+ "x": 0,
1863
+ "y": 66
1864
+ },
1700
1865
  "id": 104,
1701
1866
  "panels": [],
1702
1867
  "title": "Lineage logs",
@@ -1768,7 +1933,7 @@
1768
1933
  "h": 10,
1769
1934
  "w": 24,
1770
1935
  "x": 0,
1771
- "y": 58
1936
+ "y": 67
1772
1937
  },
1773
1938
  "id": 6,
1774
1939
  "options": {
@@ -103,11 +103,24 @@ groups:
103
103
  runbook: "Severity guidance from production docs: warning."
104
104
  - alert: ObservMeActiveAgentsStuckHigh
105
105
  expr: >-
106
- sum(observme_active_agents) > 100
106
+ (sum(max by (observme_instance_id) ((observme_active_agents > 0) and on (observme_instance_id)
107
+ ((observme_agent_lease_expires_unixtime_seconds > time()) and
108
+ (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or vector(0)) > 100
107
109
  for: 30m
108
110
  labels:
109
111
  severity: deployment-dependent
110
112
  annotations:
111
113
  summary: Active agents stuck high
112
- description: ObservMe active-agent gauge remains above 100.
113
- runbook: "Severity guidance from production docs: depends on normal fleet size; tune per deployment."
114
+ description: ObservMe leased active-agent count remains above 100; expired, missing, and pathological leases fail closed.
115
+ runbook: "Severity guidance from production docs: depends on normal fleet size; tune per deployment. Compare leased activity with raw and expired claims plus Export Health."
116
+ - alert: ObservMeExpiredActiveAgentClaims
117
+ expr: >-
118
+ (sum(max by (observme_instance_id) ((observme_active_agents > 0) and on (observme_instance_id)
119
+ (observme_agent_lease_expires_unixtime_seconds <= time()))) or vector(0)) > 5
120
+ for: 15m
121
+ labels:
122
+ severity: deployment-dependent
123
+ annotations:
124
+ summary: Expired active-agent claims detected
125
+ description: More than five positive lifecycle claims have expired leases for 15 minutes; they are stale diagnostics and excluded from leased active totals.
126
+ runbook: "Deployment-tunable diagnostic: compare Raw active claims, Expired active claims, leased Active agents, and Export Health; investigate abrupt producer death, export gaps, and Collector cleanup. /obs agents is local child state, not fleet truth."
@@ -1075,7 +1075,7 @@
1075
1075
  "type": "prometheus",
1076
1076
  "uid": "prometheus"
1077
1077
  },
1078
- "description": "Currently active ObservMe agent runtimes. Zero can mean healthy idle. Red at 100 references the tunable ObservMeActiveAgentsStuckHigh default; yellow at 50 is an early-warning assumption. Compare sustained values with fan-out and depth.",
1078
+ "description": "Canonical leased-active ObservMe runtimes. Clean shutdown reaches zero after export/scrape propagation; after a crash, SIGKILL, cancelled job, or runner loss, the leased total converges to zero within the default 60-second lease plus one Prometheus scrape even if raw claims stay cached. Zero can also mean healthy idle or a failed-closed lease, so check Export Health. /obs agents reports only current-process child state, not fleet liveness. Red at 100 matches the deployment-tunable ObservMeActiveAgentsStuckHigh threshold; yellow at 50 is an early warning.",
1079
1079
  "fieldConfig": {
1080
1080
  "defaults": {
1081
1081
  "color": {
@@ -1141,7 +1141,7 @@
1141
1141
  "uid": "prometheus"
1142
1142
  },
1143
1143
  "editorMode": "code",
1144
- "expr": "sum(observme_active_agents) or vector(0)",
1144
+ "expr": "sum(max by (observme_instance_id) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or vector(0)",
1145
1145
  "range": true,
1146
1146
  "refId": "A"
1147
1147
  }
@@ -1998,7 +1998,7 @@
1998
1998
  "type": "prometheus",
1999
1999
  "uid": "prometheus"
2000
2000
  },
2001
- "description": "Current active agents by bounded role and depth. No rows can mean healthy idle or an absent active-agent gauge; compare with the ungrouped Active agents chip and Export Health before treating it as healthy.",
2001
+ "description": "Canonical leased-active agents by emitted bounded role and environment. Clean shutdown reaches zero after export/scrape propagation; after an ungraceful exit, leased totals converge to zero within the default 60-second lease plus one Prometheus scrape while raw claims may remain cached. Depth is omitted because active signals do not emit subagent_depth. An unlabeled zero means healthy idle or no qualifying lease when Prometheus is reachable; compare Export Health. /obs agents is current-process child state, not this aggregate fleet view.",
2002
2002
  "fieldConfig": {
2003
2003
  "defaults": {
2004
2004
  "color": {
@@ -2058,14 +2058,14 @@
2058
2058
  "uid": "prometheus"
2059
2059
  },
2060
2060
  "editorMode": "code",
2061
- "expr": "sum(observme_active_agents) by (agent_role, subagent_depth)",
2061
+ "expr": "sum by (agent_role, environment) (max by (observme_instance_id, agent_role, environment) ((observme_active_agents > 0) and on (observme_instance_id) ((observme_agent_lease_expires_unixtime_seconds > time()) and (observme_agent_lease_expires_unixtime_seconds <= time() + 305)))) or on() vector(0)",
2062
2062
  "instant": true,
2063
- "legendFormat": "{{agent_role}} depth={{subagent_depth}}",
2063
+ "legendFormat": "{{agent_role}} / {{environment}}",
2064
2064
  "range": false,
2065
2065
  "refId": "A"
2066
2066
  }
2067
2067
  ],
2068
- "title": "Active agents by role/depth",
2068
+ "title": "Active agents by role/environment",
2069
2069
  "type": "bargauge"
2070
2070
  },
2071
2071
  {