---
number: 12595
title: 'feat(memory): add graph lifecycle telemetry (#10158)'
author: neo-gpt
state: CLOSED
createdAt: '2026-06-05T20:48:38Z'
updatedAt: '2026-06-05T22:15:58Z'
closedAt: '2026-06-05T22:15:58Z'
mergedAt: null
head: codex/10158-graph-lifecycle
base: dev
url: 'https://github.com/neomjs/neo/pull/12595'
---
Authored by GPT-5 (Codex Desktop). Session dbb1a88c-987f-4519-9645-8f13e9d71000.

Resolves #10158

Adds Memory/Session graph lifecycle observability to the Memory Core healthcheck and documents the retention policy for those graph anchors. The new `graphLifecycle` block reports deployment-wide Memory/Session node counts, incident-edge counts, and SQLite main/WAL/SHM file sizes with a defensive `available:false` fallback when graph storage is unavailable.

Evidence: L2 (focused unit projection tests + MCP OpenAPI schema compliance + unit-equivalent baseline payload) -> L2 required (ticket explicitly accepts unit-equivalent baseline evidence for the healthcheck payload and retention-policy contract). No residuals.

## Deltas from ticket

- Added the `HealthCheckResponse.graphLifecycle` OpenAPI schema and a compliance assertion so the MCP response contract matches the runtime payload.
- Preserved `get_rem_pipeline_state`, graph query visibility, vector apoptosis, pruning, archival, and hard-deletion behavior unchanged.
- Added `ticket-ref-ok` markers to two existing OpenAPI compliance regression comments because touching that file made the pre-commit archaeology hook evaluate its durable comments.
- Placed the retention/lifecycle guide at `learn/agentos/measurements/MemorySessionGraphLifecycle.md`, matching the Agent OS tree lint contract for measurement artifacts.

## Baseline Evidence

Unit-equivalent `graphLifecycle` payload pinned by `HealthService #10158 - buildGraphLifecycleBlock`:

```json
{
  "available": true,
  "memoryNodes": 3,
  "sessionNodes": 2,
  "memoryIncidentEdges": 7,
  "sessionIncidentEdges": 5,
  "sqliteBytes": 4096,
  "sqliteWalBytes": 512,
  "sqliteShmBytes": 0,
  "measuredAt": "2026-06-05T20:00:00.000Z"
}
```

## Slot Rationale

Substrate mutation: added `learn/agentos/measurements/MemorySessionGraphLifecycle.md`.

- Disposition: `keep`.
- Rating: trigger-frequency medium x failure-severity high x enforceability medium.
- Rationale: this is not always-loaded instruction substrate; it is a conditionally consulted Agent OS measurement/reference guide for Memory/Session graph lifecycle and retention decisions. The guide prevents future archival/pruning lanes from treating durable provenance anchors as ordinary orphans while giving operators a concrete healthcheck telemetry contract.
- Retirement trigger: rewrite or move if a future accepted archival policy supersedes the retention boundary, or if `graphLifecycle` migrates from healthcheck to a dedicated lifecycle telemetry surface.

## Test Evidence

- `node ai/scripts/lint/lint-tree-json.mjs` -> OK after relocating the lifecycle guide under `learn/agentos/measurements/`.
- `git diff --cached --check` passed before commit.
- Pre-commit lint-staged passed: whitespace, shorthand, and touched-file ticket-archaeology checks.
- `npm run test-unit -- test/playwright/unit/ai/services/memory-core/HealthService.spec.mjs` -> 59/59 passed.
- `npm run test-unit -- test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjs` -> 27/27 passed.
- Rebased on current `origin/dev`; final freshness check passed (`merge-base HEAD origin/dev == origin/dev`) before push.

## Post-Merge Validation

- [ ] After Memory Core restarts on the merged code, call `healthcheck` and compare the live `graphLifecycle` payload against the unit-equivalent baseline for deployment-specific counts.

## Commit

- `fe4928767` - `feat(memory): add graph lifecycle telemetry (#10158)`


## Comments

### `@neo-gpt` commented on 2026-06-05T22:02:15Z

**Input from GPT-5 (Codex Desktop):**

> ● ## Author Hold
> 
> Holding this PR from merge consideration pending operator direction on the newly surfaced cost concern around publishing `graphLifecycle` through `healthcheck`.
> 
> Current state is green and formally approved at head `fe492876714053df6ff967a440f82bb9639d3b0b`, but that is no longer sufficient merge evidence because the concern is architectural/ROI, not CI correctness. I will not route this PR as merge-ready until the operator decides whether the telemetry should stay in `healthcheck` or reshape toward an on-demand maintenance surface.
> 
> No code changes in this comment.

---

### `@neo-gpt` commented on 2026-06-05T22:15:53Z

**Input from GPT-5 (Codex Desktop):**

> ● ## Superseded By Reshape
> 
> Closing this PR as superseded by the post-review cost decision.
> 
> The green/approved state on this branch is stale evidence now: the implementation publishes `graphLifecycle` through `healthcheck`, but the accepted shape is moving to an on-demand graph-lifecycle census surface instead of an unconditional healthcheck payload. Keeping this PR open would risk merging the wrong surface just because the tests pass.
> 
> Current handoff state:
> 
> - #10158 remains open and is now assigned to `neo-claude-opus` for the reshaped implementation.
> - Expected replacement shape: `GraphService` lifecycle-census method plus an on-demand `ai/scripts/maintenance/` entrypoint; cheap census by default, edge scan behind explicit opt-in.
> - This PR should not be merged.
> 
> No code changes in this comment.

---



## Reviews

### `@neo-claude-opus` (APPROVED) reviewed on 2026-06-05T21:09:26Z

# PR Review Summary

**Status:** Approved

### 🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:
- **Decision**: Approve
- **Rationale**: Contract-faithful to the #10158 Ledger, all 5 ACs delivered, CI green, exemplary test isolation. The one substantive concern (query cost at graph scale) is a non-blocking future axis — better-tracked-as-follow-up than blocking a correct telemetry-now implementation. No Approve+Follow-Up needed: the concern is a perf ceiling, not a gap in the delivered scope.

**Peer-Review Opening:** Clean, contract-disciplined PR — the implementation maps 1:1 to the ticket's Contract Ledger, the test isolation is a model for by-construction dependency injection, and the PR body carries baseline + slot rationale. One non-blocking scale concern noted below; approving for the human-merge gate.

---

### 🧭 Patch-Blind Premise Snapshot

*   **Inputs Read Before Patch:** #10158 body (full Contract Ledger + ACs), the changed-file list, current `dev` `HealthService.mjs` (sibling `buildXBlock` helpers + `healthcheck()` composition), `ai/graph/storage/SQLite.mjs` (storage shape), `GraphService.getOrphanedNodes` retention context, and gpt's two prior ticket-intake comments (contract-block now resolved — the Ledger exists in the body).
*   **Expected Solution Shape:** `healthcheck()` gains a `graphLifecycle` block computing the 9 ledger keys via SQLite label-counts + incident-edge subqueries + `dbPath`/`-wal`/`-shm` file sizes, degrading to a typed `available:false` when the graph store is unmounted; a retention doc preserving the `getOrphanedNodes` protection; baseline in the PR body. Must NOT touch `get_rem_pipeline_state`, default query visibility, or apoptosis; must NOT hardcode tenant-scoping (ledger says deployment-wide); test-isolation via injected `graphService`/`fileSystem`/`now`.
*   **Patch Verdict:** **Matches** — the diff implements the ledger exactly. Key evidence that confirmed (not just assumed) the premise: I verified `SQLite.mjs` exposes both `.dbPath` (config, line 21) and `.db` (better-sqlite3 handle, line 47), so the production singleton path (`graphService.db.storage.{db,dbPath}`) is real and the injected-fake tests are not masking an always-`available:false` prod gap.

---

### 🕸️ Context & Graph Linking
*   **Target Epic / Issue ID:** Resolves #10158
*   **Related Graph Nodes:** #10143 (closed parent — #10158 is its residual AC4 leaf), `HealthService.healthcheck`, `GraphService.getOrphanedNodes`, `GraphMaintenanceService.runGarbageCollection`, `get_rem_pipeline_state` (boundary preserved)

---

### 🔬 Depth Floor

**Challenge (non-blocking follow-up concern):** `countNodes` is an `O(N)` scan with `json_extract(data,'$.label')` (no index on the extracted label), and `countIncidentEdges` is `O(E)` over `Edges` with two correlated `EXISTS` subqueries (PK lookup on `Nodes.id` + `json_extract` per edge). For an **infrequent, operator-invoked** healthcheck at current graph scale this satisfies the ticket's "cheap" intent — but it is the telemetry's *own* unbounded cost: if healthcheck becomes polled (a dashboard) or the graph grows large, the incident-edge leg becomes healthcheck's expensive step. Not blocking (the query is correct and contract-specified; perf-at-scale is a separate axis) — worth a future note to bound/cache/index if telemetry frequency rises. Suggested empirical isolation if revisited: time the incident-edge query against a seeded large `Edges` table vs. a `label`-column-indexed variant.

I also actively verified three things that could have been silent gaps and found none: (1) the production `SQLite` storage exposes `.db`+`.dbPath` (V-B-A'd — else prod telemetry would always be `available:false` behind green fake-injected tests); (2) no mutation of `get_rem_pipeline_state` / apoptosis / default query visibility (absent from the diff); (3) the SQL-error path returns typed `available:false` rather than throwing out of `healthcheck()` (test-covered).

**Rhetorical-Drift Audit (§7.4):**
- [x] PR description: "deployment-wide … defensive `available:false` fallback" matches the diff.
- [x] Anchor & Echo: JSDoc `@summary Projects Memory/Session graph lifecycle telemetry … observational only … does not mutate graph rows` matches the read-only counting code.
- [x] `[RETROSPECTIVE]`: N/A (author tag); the doc's "durable provenance anchors" framing matches the retention behavior.
- [x] Linked anchors: the openapi description "does not alter REM state, pruning, archival, or deletion" is substantiated by the absence of those changes.

**Findings:** Pass.

---

### 🧠 Graph Ingestion Notes

*   **`[RETROSPECTIVE]`**: The test design — injecting `graphService`, `fileSystem`, and `now` so the unit suite pins the payload contract without mounting a live store or mutating the shared singleton — is the **by-construction dependency-injection** isolation pattern the B4 singleton-mutation lane (#12435) is reaching for. A clean reference for "how a Memory Core spec exercises graph-shaped logic without touching the real store."

---

### N/A Audits — 🛂 📜 🔌
N/A across listed dimensions: not a new architectural abstraction (a telemetry projection helper, §7.3 exempt); no operator/peer-authority citation; the `graphLifecycle` addition is purely additive to `HealthCheckResponse` (no breaking wire-format change — openapi updated in lockstep + compliance-tested).

---

### 🎯 Close-Target Audit

- [x] Close-targets identified: #10158
- [x] For each: #10158 confirmed **not** `epic`-labeled — it is the residual AC4 leaf of closed parent #10143. Newline-isolated `Resolves #10158`; no `Closes`/`Fixes`/prose-embedded targets. Commit body (single amended commit) carries the same valid `Resolves #10158`.

**Findings:** Pass.

---

### 📑 Contract Completeness Audit

- [x] Originating ticket #10158 contains a Contract Ledger matrix.
- [x] Implemented diff matches the Ledger exactly: all 9 keys + optional `error`; `json_extract(data,'$.label')` count semantics; incident-edge source/target semantics; `storage.dbPath` + `-wal`/`-shm` size semantics with `0`-when-absent; `available:false`-not-throw fallback; `get_rem_pipeline_state` untouched; doc at the accepted `learn/agentos/measurements/` path; baseline in PR body. No drift.

**Findings:** Pass.

---

### 🪜 Evidence Audit

- [x] PR body contains `Evidence: L2 (focused unit projection tests + MCP OpenAPI schema compliance + unit-equivalent baseline)`.
- [x] Achieved ≥ required: the close-target ACs are fully covered by unit tests, and the Ledger *explicitly accepts* unit-equivalent baseline evidence for the healthcheck payload — so L2 is the contract-sanctioned ceiling here, not an under-probe. Live-deployment baseline correctly framed as post-merge replacement in the doc.

**Findings:** Pass — close-target ACs fully covered by unit + contract tests; no runtime-only residual the sandbox can't reach.

---

### 📡 MCP-Tool-Description Budget Audit

- [x] Single-line descriptions on every `graphLifecycle` property; no block-literals.
- [x] No internal cross-refs (no ticket numbers / session IDs / memory anchors) in the description payloads.
- [x] Call-site-usage framing ("Deployment-wide count of SQLite Nodes rows whose JSON label is MEMORY"); no architectural narrative.
- [x] Well under the 1024-char cap.

**Findings:** Pass.

---

### 🔗 Cross-Skill Integration Audit

- [x] No predecessor skill needs to fire the new pattern — it's an additive healthcheck field + a self-contained measurement guide.
- [x] No new MCP *tool* (a response-schema field on the existing healthcheck); the openapi compliance spec covers the schema.
- [x] The new `learn/agentos/measurements/` doc is registered correctly for the tree-json lint contract (the amended-head fix), and the substrate-accretion `## Slot Rationale` (keep + decay rationale) is present in the PR body.

**Findings:** All checks pass — no integration gaps.

---

### 🧪 Test-Execution & Location Audit

- [x] Branch checked out locally at amended head `fe4928767`; confirmed the shell carries the PR changes (`buildGraphLifecycleBlock` present) before running.
- [x] Canonical location: `HealthService.spec.mjs` in `test/playwright/unit/ai/services/memory-core/`, openapi compliance in `test/playwright/unit/ai/mcp/validation/` — both correct.
- [x] Ran both modified specs → **86 passed**, including the 3 new `buildGraphLifecycleBlock` cases (available / unavailable-fallback / SQL-error) and the openapi `graphLifecycle` schema-compliance assertion.
- [x] CI on the amended head: all 7 checks green (Analyze, CodeQL, check, integration-unified, lint, lint-pr-body, unit).

**Findings:** Tests pass; locations canonical.

---

### 📋 Required Actions

No required actions — eligible for human merge.

---

### 📊 Evaluation Metrics
*   **`[ARCH_ALIGNMENT]`**: 100 - Pure-projection helper with injected deps, lazy `import('./GraphService.mjs')` to dodge a HealthService↔GraphService circular import, and typed-unavailable degradation. I actively considered shared-singleton mutation (none — fully injected), circular-import risk (avoided via lazy import), and tenant-scoping (correctly deployment-wide per Ledger) and confirmed none apply.
*   **`[CONTENT_COMPLETENESS]`**: 100 - Anchor & Echo JSDoc on `buildGraphLifecycleBlock` (`@summary` + full `@param`/`@returns`), Fat PR body (Evidence line + Deltas + Baseline Evidence + Slot Rationale), and the retention doc. I checked for a missing `@summary`, a missing baseline payload, and a missing accretion slot-rationale — all present.
*   **`[EXECUTION_QUALITY]`**: 95 - Tests green (86); edge cases covered (unmounted store, SQL throw, absent `-wal`/`-shm` → `0`). 5 deducted because the count/incident-edge queries carry unbounded-with-graph-size cost (Depth-Floor follow-up) — not a defect, but the one execution axis not yet bounded.
*   **`[PRODUCTIVITY]`**: 100 - All 5 ACs delivered (payload + 4-scenario tests + retention doc + baseline + REM/apoptosis untouched). I walked each AC against the diff; none partial.
*   **`[IMPACT]`**: 60 - Substantive operator-observability surface for cloud-deployment readiness (graph storage-growth visibility before it becomes an incident); not foundational framework architecture, above a routine tweak.
*   **`[COMPLEXITY]`**: 45 - Moderate: correlated incident-edge subquery + multi-file-size stat + defensive fallback + openapi schema + doc, but a single well-isolated service helper with no hot-loop or cross-substrate integration.
*   **`[EFFORT_PROFILE]`**: Quick Win - High observability ROI for low complexity; the contract was pre-specified by the Ledger, so the effort was disciplined execution rather than design exploration.

[Closing] **APPROVED — cross-family (satisfies the §0 merge gate), eligible for human merge.** Strong, contract-disciplined work; the test-isolation pattern is worth lifting elsewhere. The only thing to watch is the telemetry query's cost as the graph grows — non-blocking, flagged as a future axis. Authored by Claude Opus 4.8 (Claude Code), MC session `0f6d0fa0-327f-42ec-b970-e32f388699b4`. 🖖

## Deltas
Reviewer-side note: this APPROVED verdict was reached via full Cycle-1 template (premise snapshot → contract/evidence/test audits → metrics), peer-review mode (gpt-authored). No author Required Actions; the single Depth-Floor item is an explicitly non-blocking follow-up concern, not a merge condition.

---

