> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oktolabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Archive & retention

> Reference for the 2 cascading entity-archive Pulse MCP tools (archive_tree, restore_tree), how archival interacts with the Knowledge Graph, and the KG's retention model — supersedence as soft-archive, decay tick, dead-letter TTL, and audit-row immutability.

# Archive & retention

Pulse never hard-deletes graph data. Two layers cooperate:

* **Entity archive** — `archive_tree` / `restore_tree` mark a Pulse entity (ideation, refinement, spec, sprint, card) and its children as archived. Their KG nodes stay queryable.
* **KG retention** — `Decision`, `Constraint`, and other graph nodes are retired through `supersedes` edges, not by deletion. A daily decay tick reduces stale-node relevance scores. Dead-letter entries linger until an operator inspects them.

The result is a graph that is durable across team changes, model upgrades, and entity-level cleanups.

For the schema and tool index, see [overview](/reference/mcp/kg/overview). For the consolidation flow that writes nodes, see [consolidation](/reference/mcp/kg/consolidation).

***

## Entity archive — the 2 tools

Source: `okto-pulse-core/src/okto_pulse/core/mcp/server.py`. Citations: `80-pulse-feature-inventory.md:392–393`, `:566–567`, `:814–848`.

### `okto_pulse_archive_tree`

Archive an entity and all of its children (cascading soft-delete). The entity is hidden from default views but every row remains in the database.

`server.py:6733`. Permission: the entity-scoped `<entity>.entity.archive` flag in the granular registry — e.g. `spec.entity.archive` for archiving a spec, `card.entity.archive` for archiving a card.

```text theme={null}
Input:
  board_id:    "brd_abc123"
  entity_type: "spec"        # ideation | refinement | spec | sprint | card
  entity_id:   "spec_007"
  reason:      "Replaced by spec_011 — webhook delivery v3"   (optional)
```

```json theme={null}
Output:
{
  "board_id":     "brd_abc123",
  "entity_type":  "spec",
  "entity_id":    "spec_007",
  "archived_at":  "2026-05-07T14:00:00Z",
  "archived_by":  "agent_cognitive_01",
  "cascade": {
    "ideations":    [],
    "refinements":  [],
    "specs":        ["spec_007"],
    "sprints":      ["sprint_007a"],
    "cards":        ["card_91", "card_92", "card_93"]
  },
  "kg_impact": {
    "nodes_kept":         42,
    "nodes_marked_stale": 0
  }
}
```

The cascade follows the standard parent→children rules:

| Root       | Children archived along with it                               |
| ---------- | ------------------------------------------------------------- |
| Ideation   | Linked refinements (only if not also linked elsewhere)        |
| Refinement | Specs derived from this refinement (only if not multi-parent) |
| Spec       | Sprints scoped to this spec, plus their cards                 |
| Sprint     | Cards in this sprint                                          |
| Card       | (leaf — no cascade)                                           |

`kg_impact.nodes_kept` reports how many graph nodes reference the archived tree by `belongs_to`. **Those nodes are not removed.** Their decisions, constraints, and learnings remain queryable by every read tool — including across boards via [`kg_query_global`](/reference/mcp/kg/queries#kg_query_global).

### `okto_pulse_restore_tree`

Reverse of `archive_tree`. Restores the entity and every cascade entry that was archived in the same operation.

`server.py:6777`. Permission: the entity-scoped `<entity>.entity.restore` flag — e.g. `spec.entity.restore`.

```text theme={null}
Input:
  board_id:    "brd_abc123"
  entity_type: "spec"
  entity_id:   "spec_007"
```

```json theme={null}
Output:
{
  "board_id":    "brd_abc123",
  "entity_type": "spec",
  "entity_id":   "spec_007",
  "restored_at": "2026-05-07T14:30:00Z",
  "restored_by": "agent_cognitive_01",
  "cascade": {
    "specs":   ["spec_007"],
    "sprints": ["sprint_007a"],
    "cards":   ["card_91", "card_92", "card_93"]
  }
}
```

Restore only un-archives entries that were archived together with the root. Children archived independently (a card archived on its own before the spec was archived) stay archived.

### Permissions per entity type

`80-pulse-feature-inventory.md:814–848`. Every entity type that supports archival exposes `archive` and `restore` flags under its `<entity>.entity.*` namespace in `core/infra/permissions.py:PERMISSION_REGISTRY`:

| Entity     | Archive flag                | Restore flag                |
| ---------- | --------------------------- | --------------------------- |
| Ideation   | `ideation.entity.archive`   | `ideation.entity.restore`   |
| Refinement | `refinement.entity.archive` | `refinement.entity.restore` |
| Spec       | `spec.entity.archive`       | `spec.entity.restore`       |
| Sprint     | `sprint.entity.archive`     | `sprint.entity.restore`     |
| Card       | `card.entity.archive`       | `card.entity.restore`       |

Cognitive agents typically have all archive/restore flags on the entity types they own. User-facing dashboards typically have them only for cards.

### Underlying REST endpoints

| Method | Path                                                   |
| ------ | ------------------------------------------------------ |
| `POST` | `/boards/{board_id}/archive/{entity_type}/{entity_id}` |
| `POST` | `/boards/{board_id}/restore/{entity_type}/{entity_id}` |

`80-pulse-feature-inventory.md:566–567`. The MCP tools wrap these directly — most callers should prefer the MCP tools.

***

## How archive interacts with the Knowledge Graph

When a Pulse entity is archived, three things happen on the KG side:

1. **Graph nodes are not deleted.** Every `Decision`, `Constraint`, `Requirement`, etc. that was consolidated from the archived entity stays in `graph.lbug`. Queries continue to return them.
2. **`belongs_to` edges remain.** The structural link from the graph node to its source artifact is preserved. Queries can still walk back to the (now-archived) entity row.
3. **Audit rows are immutable.** The consolidation audit table is append-only. An archive operation never edits or deletes audit rows.

The only KG-visible effect of archiving an entity is the count returned in `kg_impact.nodes_kept` — a count, not a delete. This is by design: a decision that turned out to be wrong is a decision worth keeping in the graph, marked as superseded, so future agents can see why the team moved away from it.

If the goal is to retire a *decision* (not the entity that produced it), the right move is a new consolidation session that adds a `supersedes` edge from the new node to the old one. See [consolidation — `add_edge_candidate`](/reference/mcp/kg/consolidation#3-okto-pulse-kg-add-edge-candidate).

***

## KG retention model

The graph is durable, but it is not append-only forever. Three mechanisms keep it useful at scale:

### Supersedence — the structural soft-archive

When a `Decision` is replaced, the new node is added with a `supersedes` edge to the old one. The old node is preserved. Queries that walk the chain (`kg_get_supersedence_chain`) return both, in order.

This is the canonical way to "retire" a decision in the KG. There is no `delete_decision` MCP tool — and there will not be one. The supersedence chain is the audit trail.

```text theme={null}
[dec_001 "No retry"]  ←supersedes—  [dec_4c20 "Fixed 3s × 5"]  ←supersedes—  [dec_8f1a "Exponential backoff with jitter"]
```

Queries default to the head of the chain, but the full history is always reachable.

### Decay tick — relevance, not deletion

The decay tick worker (`kg/workers/kg_decay_tick.py`) runs daily by default (`kg_decay_tick_interval_minutes = 1440`). For each node whose `last_recomputed_at` is older than `kg_decay_tick_staleness_days` (default 7), it recomputes `relevance_score` based on age, citation count, and edge centrality.

Stale nodes don't disappear — they sink in default rankings. `kg_find_similar_decisions` and `kg_query_natural` return higher-relevance nodes first. Direct queries (`kg_get_decision_history`, `kg_query_cypher`) still return everything.

To force a recompute without waiting for the schedule, use [`kg_tick_run_now`](/reference/mcp/kg/health#okto-pulse-kg-tick-run-now).

### Dead-letter retention

Failed consolidation entries land in a dead-letter table after `kg_queue_max_attempts` retries. They are kept indefinitely until an operator either:

* Calls [`kg_dead_letter_reprocess`](/reference/mcp/kg/health#okto-pulse-kg-dead-letter-reprocess) to retry them, or
* Decides the underlying source no longer exists and removes the row directly from the database.

There is no automatic dead-letter purge. This is a deliberate choice — silent loss of consolidation work is worse than a slowly-growing inspection queue.

### Audit-row immutability

The consolidation audit table is append-only. Every `kg_commit_consolidation` call writes one row containing the session id, content hash, every candidate, every reconciliation hint, every override, and the agent identity. Archiving a Pulse entity does not edit or delete audit rows. Migrating the schema does not edit audit rows. The graph state can be reconstructed from the audit table at any point in history.

***

## Retention vs. archive — pick the right tool

| You want to…                                                              | Tool                                                                                                      |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Hide a spec from the default board view, but keep its decisions queryable | `okto_pulse_archive_tree`                                                                                 |
| Bring back a spec that was archived                                       | `okto_pulse_restore_tree`                                                                                 |
| Retire a decision while preserving the rationale chain                    | New consolidation session with a `supersedes` edge — see [consolidation](/reference/mcp/kg/consolidation) |
| Reduce ranking weight of stale decisions                                  | `okto_pulse_kg_tick_run_now` (or wait for the daily schedule)                                             |
| Remove a dead-letter entry that will never succeed                        | DB-level delete (no MCP tool — by design)                                                                 |

***

## Common scenarios

### "We retired a feature — archive its specs but keep the decisions"

1. `archive_tree` on each retired spec. Cascade carries sprints and cards.
2. KG nodes for those specs (`Decision`, `Constraint`, `Requirement`) stay. They are returned by every read tool.
3. To make the supersedence explicit, open a consolidation session and add `supersedes` edges from the replacement specs' decisions to the retired ones.

### "We archived the wrong spec"

1. `restore_tree` on the same `(entity_type, entity_id)`. The full cascade restores.
2. Any KG nodes consolidated since the archive remain — restore is a `belongs_to` flip, not a graph rewrite.

### "Old decisions are dominating new search results"

1. Force a decay tick: `kg_tick_run_now`.
2. If still ranking high, the issue is recency weighting in `find_similar_decisions` (0.2 × recency in the formula at `kg_service.py:244`). Use `kg_get_decision_history` with `since` filter for time-bounded results.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Overview" href="/reference/mcp/kg/overview">
    Schema, storage layout, and the full 26-tool index.
  </Card>

  <Card title="Health & migration" href="/reference/mcp/kg/health">
    Operate the consolidation pipeline, dead-letter, and decay tick.
  </Card>
</CardGroup>
