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. For the consolidation flow that writes nodes, see consolidation.
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.
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)
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.
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.
Input:
board_id: "brd_abc123"
entity_type: "spec"
entity_id: "spec_007"
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:
- 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.
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.
- 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.
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.
[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.
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 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.
| 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 |
| 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”
archive_tree on each retired spec. Cascade carries sprints and cards.
- KG nodes for those specs (
Decision, Constraint, Requirement) stay. They are returned by every read tool.
- 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”
restore_tree on the same (entity_type, entity_id). The full cascade restores.
- 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”
- Force a decay tick:
kg_tick_run_now.
- 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
Overview
Schema, storage layout, and the full 26-tool index.
Health & migration
Operate the consolidation pipeline, dead-letter, and decay tick.
Last modified on May 8, 2026