# Lineage (/docs/lineage)



Lineage is one surface in the **observability**
[thematic island](/docs/concept-clusters#what-lives-outside-the-cluster-pattern) —
siblings of [observability](/docs/observability) (the orientation
page), [OTel spans](/docs/otel-observability), and
[runtime inspector](/docs/runtime-inspector). It's read-side, after
the turn. For the write-side audit ledger (the per-call row that
joins to lineage nodes on `sessionId`), see
[Audit ledger](/docs/audit-ledger).

A session rarely exists in isolation. A user continues a previous
conversation. A time-travel branch forks from a checkpoint. A
subagent's output flows into a parent message. A long-running
batch job's result imports into a fresh thread.

`@pleach/core/lineage` records those relationships explicitly:
typed edges between sessions, plus optional artifact provenance,
queryable as a graph.

```typescript
import {
  LineageTracker,
  HARNESS_NATIVE_LINEAGE_ARTIFACT_TYPES,
} from "@pleach/core";
import type {
  SessionNode,
  LineageEdge,
  LineageRelation,
  LineageArtifact,
  SessionMetrics,
  HarnessNativeLineageArtifactType,
} from "@pleach/core";
```

<SourceMeta subpath="@pleach/core/lineage" source="{ label: &#x22;src/lineage/&#x22;, href: &#x22;https://github.com/pleachhq/core/tree/main/src/lineage&#x22; }" />

## `LineageTracker` [#lineagetracker]

Producer + consumer in one surface. Lineage is **opt-in**: pass a
`LineageTracker` as `config.lineageTracker` and the runtime records
the session node at creation and a `branched_from` edge when a fork
materializes a new session. The remaining relations
(`continued_from`, `used_result_of`, `delegated_from`, `planned_by`)
are recorded by the host at their trigger points — the tracker is a
plain `BaseStore`-backed surface you call directly. Without an
injected tracker, nothing is recorded. Consumers then walk the graph
for "every session that depends on this one," "where did this
artifact come from," "what forked off this turn."

```typescript
const tracker = new LineageTracker(store, orgId, userId);

await tracker.registerSession({
  sessionId,
  chatId,
  intent: "research",
  createdAt: new Date().toISOString(),
});

await tracker.addEdge({
  fromSessionId: parentSessionId,
  toSessionId:   childSessionId,
  relation:      "branched_from",
  artifact:      { type: "checkpoint", sourceId: checkpointId, label: "Forked from checkpoint" },
  createdAt:     new Date().toISOString(),
});
```

The tracker is constructed with a `BaseStore`, an `orgId`, and a
`userId`. Edges and nodes persist under the tenant-scoped key
path the store enforces.

## Edge relations [#edge-relations]

`LineageRelation` is a closed union. Each value carries semantic
weight the runtime and consumers can rely on.

`branched_from` is recorded by the runtime on the fork path; the
other four are recorded by the host at the trigger point shown.

| Relation         | Recorded by | Trigger                                                       | Consumer use                                                   |
| ---------------- | ----------- | ------------------------------------------------------------- | -------------------------------------------------------------- |
| `continued_from` | host        | User explicitly continues prior work                          | "Show me the conversation history including the previous chat" |
| `branched_from`  | **runtime** | Fork / time-travel branch materializes a new session          | "What experiments forked off the same starting point"          |
| `used_result_of` | host        | Session B consumed a canvas card or job result from session A | Job → chat provenance                                          |
| `delegated_from` | host        | Subagent spawned across a session boundary                    | Subagent provenance UI                                         |
| `planned_by`     | host        | Session was created to fulfill a plan step                    | Plan → execution provenance                                    |

## `SessionNode` [#sessionnode]

The graph's vertex shape, persisted at session creation via
`registerSession()`.

```typescript
interface SessionNode {
  sessionId:    string;
  chatId:       string;
  intent?:      string;       // primary intent detected at session start
  agentSpec?:   string;       // agent spec used (if subagent session)
  summary?:     string;       // first ~200 chars of the opening message
  createdAt:    string;       // ISO-8601
  completedAt?: string;       // ISO-8601, set by completeSession()
  metrics?:     SessionMetrics;
}
```

Call `completeSession(sessionId, metrics)` at end-of-session to
stamp `completedAt` and attach the aggregate metrics. This is a
host call — the runtime does not invoke `completeSession` on your
behalf, so `completedAt`/`metrics` stay unset until you record them.

## Querying the graph [#querying-the-graph]

```typescript
// All ancestors of a session (BFS, default depth 10):
const ancestors = await tracker.getAncestors(sessionId);

// All descendants:
const descendants = await tracker.getDescendants(sessionId);

// Self + ancestors + descendants + the edges between them:
const { nodes, edges } = await tracker.getChain(sessionId);

// Sessions that consumed a specific artifact:
const consumers = await tracker.findByArtifact("canvas_card", cardId);
```

`getAncestors` and `getDescendants` walk the graph
breadth-first with a `maxDepth` parameter (default 10).
`getChain` returns the full neighborhood plus the edges that
connect it. `findByArtifact` filters edges where
`artifact.type` and `artifact.sourceId` match — the read path
for "show every session that used this card."

## Artifact provenance [#artifact-provenance]

Artifacts ride on the edge — `LineageArtifact` names what was
carried forward when one session feeds another.

```typescript
interface LineageArtifact {
  type:     string;   // see HARNESS_NATIVE_LINEAGE_ARTIFACT_TYPES
  sourceId: string;   // identifier in the source session
  label:    string;   // human-readable label
}
```

The native artifact types ship as a `readonly` constant:

```typescript
import { HARNESS_NATIVE_LINEAGE_ARTIFACT_TYPES } from "@pleach/core";

HARNESS_NATIVE_LINEAGE_ARTIFACT_TYPES;
// → ["canvas_card", "job_result", "plan_step", "file", "checkpoint"]
```

The `type` field is widened to `string` so host plugins can
register domain-specific values. Consumers MUST NOT switch
exhaustively on `type` — the union is open by design.

## `SessionMetrics` [#sessionmetrics]

Per-session aggregates set by `completeSession()`. The shape is
fixed:

```typescript
interface SessionMetrics {
  toolCalls:        number;
  tokenUsage:       number;
  duration:         number;  // ms
  subagentsSpawned: number;
}
```

`toolCalls`, `subagentsSpawned`, and `tokenUsage` are the
variable surface — they're what the lineage node carries that
no structural invariant can derive.

## Persistence [#persistence]

The tracker writes nodes and edges through `BaseStore` keyed on
`[orgId, userId, "lineage", "nodes" | "edges"]`. Edges key on
`"${fromSessionId}→${toSessionId}"` — re-writing the same edge
overwrites in place; the tracker doesn't validate idempotency
beyond key equality.

## Use cases [#use-cases]

| Scenario                                         | What lineage gives you                                                                |
| ------------------------------------------------ | ------------------------------------------------------------------------------------- |
| User asks "what did we talk about last time?"    | `continued_from` parents — `getAncestors` walks the chain                             |
| Stuck-session debugging fork                     | `branched_from` from a known-good checkpoint — `getDescendants` lists the experiments |
| Subagent transparency                            | `delegated_from` — show which parent spawned this session                             |
| Plan → execution provenance                      | `planned_by` — every session created to fulfill a plan step                           |
| Compliance audit "where did this card come from" | `findByArtifact("canvas_card", cardId)` — list every session that consumed it         |

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Observability" href="/docs/observability" description="OTel, Datadog, Honeycomb, Prometheus — the wider read-side wiring this graph sits inside." />

  <Card title="OTEL observability" href="/docs/otel-observability" description="Span types whose `pleach.session_id` joins to lineage nodes." />

  <Card title="Checkpointing" href="/docs/checkpointing" description="`branched_from` edges fire when a checkpoint restore creates a branch." />

  <Card title="Subagents" href="/docs/subagents" description="`delegated_from` edges record subagent → parent provenance." />

  <Card title="Audit ledger" href="/docs/audit-ledger" description="The write-side per-call row carries the `sessionId` that joins to lineage." />
</Cards>
