> For the complete documentation index, see [llms.txt](https://docs.kula.digital/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kula.digital/for-developers/graph.md).

# Graph & relationship tools

The relationship graph — member–staff–location edges, connection tiers and connection-strength scores — and the purpose-scoped tools that read who is connected to whom and how healthy those connection

Kula Intelligence derives a **relationship graph** from the attendance and sales data your [connectors](/your-data-sources/connectors.md) already feed in. It is a read-only analytics layer: no new data, no external calls — just a scored view of who trains with whom, who depends on which coach, and how healthy each of those connections is.

> **What the graph is for.** The graph tells you *who is connected to whom and how healthy that connection is* — the relationship context behind a member or staff member. It is **not a churn-risk predictor**. Spotting members who have started dropping off is the job of the **attention signals** (a separate sense layer that future [Whispers](#attention-signals) will surface) and of the **at-risk-members skill**, both of which read from — but are distinct from — the relationship graph.

This page covers what the graph models, how it scores the strength of each relationship, and the tools that read it. For the raw tables, see the [schema reference](/for-developers/schema.md).

## What the graph models

The graph is **tripartite** — three entity types (members, staff, locations) connected by five classes of weighted edge:

| Edge class | Between           | Captures                                  |
| ---------- | ----------------- | ----------------------------------------- |
| **MS**     | member ↔ staff    | who a member trains with — the coach bond |
| **ML**     | member ↔ location | attachment to the studio/space itself     |
| **MM**     | member ↔ member   | the peer community (who attends together) |
| **SL**     | staff ↔ location  | where a coach works                       |
| **SS**     | staff ↔ staff     | the cover / co-teach network              |

Edges are **time-decayed** (recent activity counts for more than old) and **windowed** (stale relationships age out), so the graph reflects *current* relationships, not lifetime totals. Each edge class decays on its own half-life and ages out at its own **drop-off window** — a coach bond (member↔staff) fades faster than facility/organisational bonds. Every edge carries a **tier** (`strong`/`warming`/`cooling`/`cold`/`lapsed`) derived from how recently it was last seen plus its 30-day **trend**, so you can read *where* a relationship is decaying and over *what timeframe* — see `get_staff_relationship_review` for the per-teacher breakdown.

## Connection strength: resilience and quadrants

On top of the edges, the graph computes a per-member, per-location **connection-strength** score (stored as `resilience`): how well-anchored a member is beyond any single coach — i.e. how well their relationship with the studio would hold up if their favourite coach left. A member anchored by the location, their peers, and a varied routine has a strong connection; one held only by a single coach bond has a fragile one.

Connection strength is **constraint-modulated** by two factors:

* **Activity affinity (A)** — how broadly the member uses the location (many instructors and classes vs. a single specialist slot).
* **Contract commitment (C)** — the switching cost of their plan (a recurring membership vs. pay-as-you-go).

Together, A and C place each member in one of four **connection-strength quadrants** (the `vuln_quadrant` field):

| Quadrant               | Affinity | Commitment | How to read it                                                     |
| ---------------------- | -------- | ---------- | ------------------------------------------------------------------ |
| `resilient_enthusiast` | high     | high       | Strongest connection — broad routine, committed plan.              |
| `constrained_captive`  | low      | high       | Committed but narrow — a single-coach specialist on a sticky plan. |
| `flexible_explorer`    | high     | low        | Broadly engaged, but low switching cost.                           |
| `specialist_nomad`     | low      | low        | **Weakest connection** — narrow routine, no commitment.            |

> The exact scoring is tuned per deployment and not published. The tools return the resulting scores and quadrant labels so you can act on them; the [`list_quadrant_members`](#list_quadrant_members) tool lists the actual members in any quadrant.

There is also a per-**location** operational resilience: the share of staff who have a cover partner, and the single points of failure (coaches who have none). Read it with [`get_location_context`](#get_location_context).

## Attention signals

Separately from the relationship scores above, the graph also emits an **attention-signal queue** — derived facts that a member may have started dropping off and could need attention now. This is the **sense layer**, distinct from the relationship context: it flags a *change in behaviour*, not the strength of a connection. Read the open queue with [`get_open_signals`](#get_open_signals). Future **Whispers** will surface these to front-line staff, and the separate **at-risk-members skill** turns them into an outreach list — the graph itself only exposes the signals.

| Signal                | Fires when                                                                                                           |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `drift`               | A previously-regular member has gone quiet for a stretch.                                                            |
| `freq_softening`      | Attendance cadence is slowing while they're still showing up — the early warning before drift.                       |
| `missed_second_visit` | They paid, came once, and never returned.                                                                            |
| `pause_drift`         | A paused member isn't resuming.                                                                                      |
| `at_risk_threshold`   | Their constraint-modulated resilience has crossed a low threshold — a structural signal, not just a behavioural gap. |
| `milestone`           | A round-number class count — a positive moment worth celebrating.                                                    |

> **Boundary.** Kula Intelligence *computes and exposes* these signals. It never composes or sends a message — that belongs to the studio's own action layer. Everything on this page is read-only.

## The relationship-context tools

These tools return **pre-scoped relationship context** — richer than a raw table read, and automatically filtered to what your [permission level](/connect-claude-and-access/scopes.md), your relationship to the entity, and your **declared purpose** allow.

Every call takes a required `purpose` argument, which shapes the response:

* **Analysis purposes** (`retention_analysis`, `departure_simulation`, `cohort_analysis`) **anonymise** members — names become stable pseudonyms and the underlying ids are dropped, so you can study patterns without handling identities.
* **Action purposes** (`action_board`) return real names so an operator can act.
* **Service-delivery purposes** (`whisper_generation`, `pre_class_brief`) **withhold the connection-strength analytics** (the resilience and quadrant scores) — those are for analysis, not front-line delivery.

Other accepted purposes: `ai_coach_session`, `member_self_access`, `staff_self_access`. A denied request returns an **empty result**, never an error that would confirm the entity exists.

| Tool                            | Min level  | What it does                                                                                                                                                                     |
| ------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_member_context`            | operations | One member's bundle: core facts, per-location connection strength (resilience + quadrant), and affinity routing (including whether the primary coach is their only one).         |
| `get_staff_context`             | operations | A staff member's profile, concentration (members who rely on them, and how many have no other coach), and the locations they work.                                               |
| `get_staff_relationship_review` | operations | A teacher's members bucketed by relationship tier (strong/warming/cooling/cold/lapsed) with each bond's timeframe and trend — the connection-decay breakdown for a staff review. |
| `get_location_context`          | operations | A location's operational resilience and the *aggregate* spread of members across the four connection-strength quadrants.                                                         |
| `list_quadrant_members`         | operations | The members **in** a quadrant at a location — the act-on-it drill-down from the aggregate.                                                                                       |
| `simulate_departure`            | operations | Which members would lose their strongest connection if a given staff member left a location, ordered weakest-connection first.                                                   |
| `get_entity_edges`              | operations | The raw edges around a member/staff/location, with class, weight and recency.                                                                                                    |

### `get_member_context`

One member's full relationship picture. **Parameters:** `member_id` (required), `purpose` (required). Returns the member's core facts, per-location connection strength (the `resilience` score, activity affinity, contract commitment, `vuln_quadrant`), the affinity routing rollup (primary coach, top-3 staff, concentration, and `is_exclusive_to_primary` — whether the primary is literally the member's only teacher), and any operator/AI annotations. The connection-strength scores are withheld under service-delivery purposes.

### `get_staff_context`

**Parameters:** `staff_id` (required), `purpose` (required). Returns the staff member's profile, their concentration rollup (how many members rely on them — routing value, how concentrated members' connections are on them, and `exclusive_member_count` — how many have no other teacher at all), and the locations they work. Heavily restricted: a relationship-bound staff credential may only query itself.

### `get_staff_relationship_review`

The connection-decay breakdown for a teacher (staff) review. **Parameters:** `staff_id` (required), `purpose` (required), `tier` (optional — restrict the member list to one of strong/warming/cooling/cold/lapsed), `limit` (optional, default 200, max 500). Returns the teacher's members grouped by relationship **tier**, and for each member: the member↔staff edge weight, `last_observed_at`, `days_since`, `trend` (rising/flat/falling), and 30-day/90-day/total attendance with this teacher. A `summary` gives the full tier distribution even when the member list is `limit`-capped. The tier is derived from how recently the member last trained with the teacher (relative to the member↔staff half-life and drop-off window) plus the 30-day trend — so "where the relationship is decaying, and over what timeframe" reads straight off the buckets. Same restriction as `get_staff_context`: a relationship-bound staff credential may only review itself. Reads precomputed `graph.edge` + `graph.connection_score` — no write.

### `get_location_context`

**Parameters:** `location_id` (required), `purpose` (required). Returns the location's core facts, operational resilience (cover depth, single points of failure), and the **aggregate** distribution of members across the four connection-strength quadrants. No member-level PII — counts only.

### `list_quadrant_members`

The act-on-it complement to `get_location_context`: where the latter gives counts, this lists the individual members. **Parameters:** `location_id` (required), `purpose` (required), `quadrant` (optional — one of the four quadrant names; omit for all classified members), `max_resilience` (optional, 0–1 — only members at or below this connection-strength ceiling, to target the weakest-connection tail), `limit` (optional, default 100, max 500). Each member comes with their `resilience`, activity affinity, contract commitment, quadrant, primary-staff routing and a `risk_category`, ordered weakest-connection first. Names follow your purpose (anonymised under analysis, real under `action_board`).

### `simulate_departure`

**Parameters:** `staff_id` (required), `location_id` (required), `purpose` (required), `limit` (optional, default 100, max 500). Models the impact if a staff member left: returns the members connected to them — those who would lose their strongest connection — each with edge weight, connection strength (constrained `resilience`) and a `risk_category`, ordered weakest-connection first. Reads precomputed weights only — no write.

### `get_entity_edges`

**Parameters:** `entity_id` (required), `entity_type` (required — `member` | `staff` | `location`), `purpose` (required), `min_weight` (optional, default 0), `limit` (optional, default 100, max 500). Returns the active edges connected to the entity, with edge class (MS/ML/SL/MM/SS), kind, weight, tier and recency.

## The summary tools

Lightweight rollup reads that don't take a purpose — handy for quick routing questions.

| Tool                      | Min level  | What it does                                                                                                                                                                                                   |
| ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_member_affinity`     | operations | A member's primary coach, top-3 staff, how concentrated their affinity is on that one coach, and whether that coach is their only one (`is_exclusive_to_primary`).                                             |
| `get_staff_concentration` | operations | Per-staff routing value: how many members' connections are anchored on this coach (members whose primary affinity is this coach), and how many of those have no other coach at all (`exclusive_member_count`). |
| `get_open_signals`        | operations | The open attention-signal queue. Optional `signal_type` filter; highest severity first.                                                                                                                        |
| `get_cac_by_cohort`       | analytics  | Customer-acquisition cost by cohort (a marketing rollup, listed here for convenience).                                                                                                                         |

## Querying the raw tables

For anything the tools don't shape for you, query the `graph.*` schema directly with `execute_query` (SELECT/`WITH` only):

```sql
-- the open attention-signal queue, most severe first
SELECT person_id, signal_type, severity, context, detected_at
FROM graph.risk_signal
WHERE resolved_at IS NULL
ORDER BY severity DESC, detected_at DESC;

-- the members with the weakest connections at a location
SELECT person_id, vuln_quadrant, resilience_constrained
FROM graph.member_resilience
WHERE location_id = '…'
ORDER BY resilience_constrained ASC;
```

See the [schema reference](/for-developers/schema.md) for the `graph.*` tables.

## Freshness

The graph is **derived**, so it lags its inputs until it rebuilds. It refreshes automatically after an import or a connector restate that changes its inputs (attendance, members, plans), and a nightly sweep is the backstop. Scores and signals therefore reflect the most recent build, not the live second.

## Where to go next

* [Tool reference](/for-developers/tools.md) — the complete tool catalogue.
* [Schema reference](/for-developers/schema.md) — the `graph.*` and other tables.
* [Permission levels](/connect-claude-and-access/scopes.md) — what each level can see.
