Skip to content

feat(intelligence): add Graph Cohesion#2978

Open
aashir-athar wants to merge 3 commits into
tinyhumansai:mainfrom
aashir-athar:feat/graph-cohesion
Open

feat(intelligence): add Graph Cohesion#2978
aashir-athar wants to merge 3 commits into
tinyhumansai:mainfrom
aashir-athar:feat/graph-cohesion

Conversation

@aashir-athar
Copy link
Copy Markdown
Contributor

@aashir-athar aashir-athar commented May 29, 2026

Summary

Adds a new read-only "Cohesion" tab to the Intelligence view: clustering-coefficient analysis of the memory knowledge graph. Where the Centrality lens answers "which entities are important", this lens answers "how tightly knit is the neighbourhood around each entity" — and surfaces a structural signal neither centrality nor a frequency sort can reveal: brokers.

A broker (structural hole) is an entity whose neighbours are not connected to each other — it's the sole link holding otherwise-separate clusters together (local clustering ≈ 0). These are the single points of failure / brokerage opportunities in the user's accumulated memory.

Design

  • Pure deterministic engine (lib/memory/graphCohesion.ts): treats the (subject)-[predicate]->(object) triples as an undirected simple graph (direction dropped, parallel edges collapsed, self-loops dropped) and computes:
    • per-node local clustering coefficient C(v) = 2·triangles / (deg·(deg-1)),
    • triangleCount, averageClustering (mean of C over degree≥2 nodes), and transitivity (the global clustering coefficient, 3·triangles / connected-triples),
    • findBrokers() — ranks the loosest-neighbourhood entities.
    • No React, no RPC, no clock, no RNG. The result depends only on graph structure, never on insertion order — and averageClustering is summed in canonical (sorted) order so it is byte-identical across input permutations despite IEEE-754 non-associativity.
  • Zero new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces JSON-RPC wrappers. Read-only — recomputed live from the graph, never persisted.
  • Container/presentational split; the container guards load-on-mount with a monotonic request token (out-of-order responses can't overwrite the latest). i18n across all 13 locales.

Test plan

  • vitest — 33 tests (engine: empty/triangle/path/4-cycle/star/diamond fixtures with hand-computed clustering, triangle, avg & transitivity values; self-loop drop; parallel-edge & direction collapse; malformed-row drop; no case-folding; byte-identical determinism across permutations; broker ranking & limits — plus api facade, panel states + broker badge + no-brokers note, container load/namespace-requery/error)
  • tsc --noEmit — clean
  • eslint — 0 errors
  • prettier --check — clean
  • i18n coverage gate — EXIT 0, no missing/extra/drifted keys across 13 locales

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Graph Cohesion tab in Intelligence with namespace selector, loading skeletons, retryable error alerts, empty-state messaging, metric tiles (entities/connections/triangles), average clustering/transitivity caption, and a ranked brokers table/badges.
  • Documentation

    • Added Graph Cohesion UI translations across many languages.
  • Tests

    • Added end-to-end tests and unit suites covering cohesion computation, broker ranking, API loading, and UI states.

Review Change Stack

@aashir-athar aashir-athar requested a review from a team May 29, 2026 19:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

Adds a deterministic graph-cohesion engine, a graphCohesion API facade, a stateful GraphCohesionTab and presentational GraphCohesionPanel, tests for computation and UI, Intelligence page wiring for a new "cohesion" tab, and i18n entries across locales.

Changes

Graph Cohesion Feature

Layer / File(s) Summary
Core graph cohesion computation
app/src/lib/memory/graphCohesion.ts, app/src/lib/memory/graphCohesion.test.ts
Deterministic cohesion metrics: builds undirected adjacency, drops self-loops/collapses parallel edges, counts triangles, computes per-node degree/triangles/localClustering, aggregates edge/triangle counts, transitivity, and deterministic averageClustering; exports CohesionNode, CohesionResult, computeGraphCohesion, and findBrokers. Tests cover basic shapes, diamond graph, normalization, determinism, sorting, and broker limits.
API facade and Intelligence page wiring
app/src/services/api/graphCohesionApi.ts, app/src/services/api/graphCohesionApi.test.ts, app/src/pages/Intelligence.tsx
graphCohesionApi.loadCohesion(namespace?) wraps memoryGraphQuery and returns computeGraphCohesion results; loadNamespaces forwards memoryListNamespaces. Intelligence page adds 'cohesion' tab variant, tabs config entry, and mounts GraphCohesionTab. API tests verify namespace passthrough, result transformation, RPC rejection propagation, and namespace listing.
Container tab and tests
app/src/components/intelligence/GraphCohesionTab.tsx, app/src/components/intelligence/GraphCohesionTab.test.tsx
Stateful tab loads cohesion and namespace lists, swallows namespace-list failures, uses a monotonic requestId to ignore stale responses, re-queries on namespace change, and wires onRetry to reload the current namespace. Tests verify initial load, namespace-driven reload, and error rendering.
Presentational panel and tests
app/src/components/intelligence/GraphCohesionPanel.tsx, app/src/components/intelligence/GraphCohesionPanel.test.tsx
Presentational React panel supporting loading skeleton, error alert with optional retry, empty state, and success view showing metric tiles (entities/connections/triangles), summary caption (average clustering, transitivity), and a broker-ranking table with clustering badges and "no brokers" note when applicable.
Internationalization support
app/src/lib/i18n/en.ts, app/src/lib/i18n/chunks/{ar,bn,de,en,es,fr,hi,id,it,ko,pt,ru,zh-CN}-1.ts
Adds memory.tab.cohesion and a graphCohesion.* block with UI strings for title/intro, loading/error/retry/empty states, namespace labels, metrics (entities, connections, triangles), summary caption (average clustering/transitivity), broker messaging, and table column headers across locales.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GraphCohesionTab
  participant graphCohesionApi
  participant memoryGraphQuery
  participant computeGraphCohesion
  participant GraphCohesionPanel
  User->>GraphCohesionTab: Open Intelligence / select cohesion tab
  GraphCohesionTab->>graphCohesionApi: loadCohesion(namespace)
  graphCohesionApi->>memoryGraphQuery: memoryGraphQuery(namespace)
  memoryGraphQuery-->>graphCohesionApi: GraphRelation[]
  graphCohesionApi->>computeGraphCohesion: computeGraphCohesion(relations)
  computeGraphCohesion-->>graphCohesionApi: CohesionResult
  graphCohesionApi-->>GraphCohesionTab: CohesionResult
  GraphCohesionTab->>GraphCohesionPanel: render(result, loading=false)
  GraphCohesionPanel-->>User: display metrics, summary, brokers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

🐰 I hopped through nodes with tiny paws,

counted ties and checked the laws,
triangles, brokers, links aligned—
cohesion maps the paths I find,
a little hop, and insights pause.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(intelligence): add Graph Cohesion' clearly and concisely describes the main change—adding a new Graph Cohesion feature to the Intelligence component. It directly matches the PR's primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. working A PR that is being worked on by the team. labels May 29, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 29, 2026
@aashir-athar aashir-athar force-pushed the feat/graph-cohesion branch from 3182427 to 42888ae Compare May 29, 2026 21:15
aashir-athar added a commit to aashir-athar/openhuman that referenced this pull request May 29, 2026
Rust Core Coverage failed in its post-step with "No space left on device"
during cache save — a runner-disk infra flake unrelated to this TS-only PR
(Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aashir-athar and others added 3 commits May 30, 2026 06:26
A new read-only "Cohesion" tab for the Intelligence view: clustering-
coefficient analysis of the memory knowledge graph. Treating the triples as an
undirected simple graph, it surfaces a structural signal the centrality and
frequency lenses cannot — BROKERS: entities whose neighbours are not connected
to each other, i.e. the sole link holding otherwise-separate clusters together.

Engine (pure, deterministic — no React/RPC/clock/RNG):
- per-node local clustering coefficient C(v) = 2·triangles / (deg·(deg-1)),
- triangleCount, averageClustering (mean over degree>=2 nodes), and transitivity
  (global clustering coefficient = 3·triangles / connected-triples),
- findBrokers() ranks the loosest-neighbourhood entities (structural holes).

Adds ZERO new core surface: composes the already-shipped memoryGraphQuery /
memoryListNamespaces JSON-RPC wrappers and delegates all math to the engine.
Container/presentational split with a monotonic request-token race guard for
load-on-mount; i18n across all 13 locales.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Rust Core Coverage job failed in its post-run cache-save step with
"No space left on device" — a runner-disk infra flake unrelated to this
TS-only change (Rust is byte-identical to main; all other Rust jobs passed).
Empty commit to re-run on a fresh runner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rust Core Coverage failed on an unrelated, flaky upstream Rust unit test
(openhuman::memory_tools::tools::put::tests::execute_defaults_unknown_priority_to_normal
— a non-deterministic "namespace/key cannot contain personal identifiers"
rule). This PR is TS-only; Rust is byte-identical to main and the same job
passes on sibling PRs. Re-running on a fresh runner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/lib/memory/graphCohesion.test.ts`:
- Around line 206-218: The inline comment in the test "sorts nodes by clustering
DESC, then degree DESC, then id ASC" is incorrect: update the note that
currently says "B,C also 1 here" to reflect that B and C have localClustering
2/3 (degree 3) and only A and D have clustering 1; edit the comment near the
computeGraphCohesion call or the rel(...) list so it accurately documents the
diamond shape and that the ones array (derived from r.nodes.filter(...).map(n =>
n.id)) should be ['A','D'].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 074eeae0-b6d9-49cc-8b4e-49110efce73e

📥 Commits

Reviewing files that changed from the base of the PR and between 42888ae and 71bc99a.

📒 Files selected for processing (23)
  • app/src/components/intelligence/GraphCohesionPanel.test.tsx
  • app/src/components/intelligence/GraphCohesionPanel.tsx
  • app/src/components/intelligence/GraphCohesionTab.test.tsx
  • app/src/components/intelligence/GraphCohesionTab.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/memory/graphCohesion.test.ts
  • app/src/lib/memory/graphCohesion.ts
  • app/src/pages/Intelligence.tsx
  • app/src/services/api/graphCohesionApi.test.ts
  • app/src/services/api/graphCohesionApi.ts
✅ Files skipped from review due to trivial changes (4)
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • app/src/components/intelligence/GraphCohesionTab.test.tsx
  • app/src/pages/Intelligence.tsx
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/services/api/graphCohesionApi.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/services/api/graphCohesionApi.test.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/components/intelligence/GraphCohesionTab.tsx
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/components/intelligence/GraphCohesionPanel.test.tsx
  • app/src/lib/memory/graphCohesion.ts
  • app/src/components/intelligence/GraphCohesionPanel.tsx

Comment on lines +206 to +218
it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => {
const r = computeGraphCohesion([
rel('A', 'B'),
rel('A', 'C'),
rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here)
rel('B', 'D'),
rel('C', 'D'), // diamond
]);
// top entries are the clustering-1 nodes; A before D by id at equal degree.
expect(r.nodes[0].localClustering).toBe(1);
const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id);
expect(ones).toEqual(['A', 'D']);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inline comment contradicts the assertion below it.

The comment claims B,C also 1 here, but in this diamond B and C have degree 3 and cluster at 2/3 — only A and D are at 1, which is exactly what the ones assertion verifies. The misleading note could prompt a future reader to "fix" a correct test.

✏️ Suggested comment correction
-      rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here)
+      rel('B', 'C'), // triangle A-B-C; A & D cluster at 1, B & C at 2/3 (diamond spine)
       rel('B', 'D'),
       rel('C', 'D'), // diamond
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => {
const r = computeGraphCohesion([
rel('A', 'B'),
rel('A', 'C'),
rel('B', 'C'), // triangle A-B-C (clustering 1 for A; B,C also 1 here)
rel('B', 'D'),
rel('C', 'D'), // diamond
]);
// top entries are the clustering-1 nodes; A before D by id at equal degree.
expect(r.nodes[0].localClustering).toBe(1);
const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id);
expect(ones).toEqual(['A', 'D']);
});
it('sorts nodes by clustering DESC, then degree DESC, then id ASC', () => {
const r = computeGraphCohesion([
rel('A', 'B'),
rel('A', 'C'),
rel('B', 'C'), // triangle A-B-C; A & D cluster at 1, B & C at 2/3 (diamond spine)
rel('B', 'D'),
rel('C', 'D'), // diamond
]);
// top entries are the clustering-1 nodes; A before D by id at equal degree.
expect(r.nodes[0].localClustering).toBe(1);
const ones = r.nodes.filter(n => n.localClustering === 1).map(n => n.id);
expect(ones).toEqual(['A', 'D']);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/lib/memory/graphCohesion.test.ts` around lines 206 - 218, The inline
comment in the test "sorts nodes by clustering DESC, then degree DESC, then id
ASC" is incorrect: update the note that currently says "B,C also 1 here" to
reflect that B and C have localClustering 2/3 (degree 3) and only A and D have
clustering 1; edit the comment near the computeGraphCohesion call or the
rel(...) list so it accurately documents the diamond shape and that the ones
array (derived from r.nodes.filter(...).map(n => n.id)) should be ['A','D'].

aashir-athar added a commit to aashir-athar/openhuman that referenced this pull request May 30, 2026
Rust Core Coverage failed in its post-step with "No space left on device"
during cache save — a runner-disk infra flake unrelated to this TS-only PR
(Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aashir-athar added a commit to aashir-athar/openhuman that referenced this pull request May 30, 2026
…rmation)

A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate
Diversity) tells you how varied the global predicate set is. The thickness
lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many
predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which
predicates dominate overall. None of them answers the question this lens
does: how much does knowing the SUBJECT predict which predicate it will
use? And which entities speak in a specialised vocabulary versus a
generalist one?

Engine (pure, deterministic — no React/RPC/clock/RNG):
- Global: I(S; P) in bits with canonical-order summation; H(S) and H(P);
  normalisedMI = I / min(H(S), H(P)).
- Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when
  D_s <= 1 — single-predicate subject is maximally specialised by
  convention), plus dominantPredicate (tie-broken by predicate ASC) and
  dominantPredicateShare.
- All p·log2(p) and joint·log2 ratio sums walk pairs in canonical
  (sortedSubjects, sortedPredicates) order so the result is byte-identical
  regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's
  float-order bug).

Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a
genuinely new lens not covered by any of the 19 shipped features).

Adds ZERO new core surface: composes the already-shipped memoryGraphQuery
/ memoryListNamespaces wrappers. Container/presentational split with a
monotonic request-token race guard for load-on-mount; i18n across all 13
locales.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aashir-athar added a commit to aashir-athar/openhuman that referenced this pull request May 30, 2026
Rust Core Coverage failed in its post-step with "No space left on device"
during cache save — a runner-disk infra flake unrelated to this TS-only PR
(Rust is byte-identical to main; the same flake hit tinyhumansai#2978 previously).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aashir-athar added a commit to aashir-athar/openhuman that referenced this pull request May 30, 2026
…rmation)

A new read-only "Specialisation" tab. The vocabulary lens (tinyhumansai#17 Predicate
Diversity) tells you how varied the global predicate set is. The thickness
lens (tinyhumansai#18 Predicate Bundles) tells you which entity pairs share many
predicates. The frequency lens (tinyhumansai#7 Relationship Types) tells you which
predicates dominate overall. None of them answers the question this lens
does: how much does knowing the SUBJECT predict which predicate it will
use? And which entities speak in a specialised vocabulary versus a
generalist one?

Engine (pure, deterministic — no React/RPC/clock/RNG):
- Global: I(S; P) in bits with canonical-order summation; H(S) and H(P);
  normalisedMI = I / min(H(S), H(P)).
- Per-subject: specialisation = 1 - H(P|S=s)/log2(D_s) in [0,1] (1 when
  D_s <= 1 — single-predicate subject is maximally specialised by
  convention), plus dominantPredicate (tie-broken by predicate ASC) and
  dominantPredicateShare.
- All p·log2(p) and joint·log2 ratio sums walk pairs in canonical
  (sortedSubjects, sortedPredicates) order so the result is byte-identical
  regardless of relation insertion order (lesson from tinyhumansai#2978 Cohesion's
  float-order bug).

Selected from the prior loop-19 design workflow's runner-up (8.35/10 — a
genuinely new lens not covered by any of the 19 shipped features).

Adds ZERO new core surface: composes the already-shipped memoryGraphQuery
/ memoryListNamespaces wrappers. Container/presentational split with a
monotonic request-token race guard for load-on-mount; i18n across all 13
locales.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant