Skip to content

Insight (InsightRoot)

Definition

An Insight — canonically an InsightRoot — is a git-like, append-only investigation repository anchored to one business subject (a customer, account, or entity). It is the root document that groups everything produced while investigating that subject: the pinned evidence (Blocks), the event ledger that records every action taken (InsightEvents), the narrative, the linked Signals that triggered or relate to it, and the sealed Editions released from it. State is never edited in place — it is derived by replaying the event ledger, and the heads map (branch → tip event id) is the set of DAG branch tips, exactly like git refs.

Note the name collision: a separate, legacy Insight object exists (ES alias insight, a lighter "finding" with status draft|active|resolved|archived). The investigation root that the platform and Beacon mean by "an investigation" is InsightRoot (alias insight_root). This spec describes InsightRoot; the legacy Insight is treated as deprecated and is one of the open questions below.

Canonical schema: cortex/docs/spec/des-objects.yml (the DES "north star", InsightRoot at :169). Runtime model: cortex/cortex/models/intelligence.py:247. Command logic: cortex/cortex/insight.py. Persistence: a thin UDS DAO (cortex/cortex/userspace/insight_root.py) over the shared Elasticsearch intelligence index, routed by the insight_root alias, with the named id (ins_ + 12 hex) used as the ES _id.

Lifecycle

status: draft → in_review → approved → published, with archived (terminal) reachable from any non-terminal state. The transition map is INSIGHT_TRANSITIONS (insight.py:66):

  • draft → {in_review, archived}
  • in_review → {approved, draft, archived}
  • approved → {published, in_review, archived}
  • published → {archived}
  • archived → {} (terminal)

Status is forward-only and human-confirmed: an attestation only suggests the next status (suggested_next_status, edition.py:1108), it does not silently advance it. Some edges auto-advance — request_edition_review bumps draft → in_review (edition.py:1519), and a self-close attestation bumps draft → published (edition.py:1045).

Closure is gated. Moving to published or archived (except the draft → archived discard) requires that all linked Signals are resolved or dismissed, at least one Edition exists, and no Tasks are open or in progress (insight.py:692). This is what makes a closed investigation auditable — it cannot be closed with loose ends.

Journey through the code

Entry wires are the MCP tools start_investigation and create_insight_from_signal (and REST POST /insight_root).

  1. Createstart_investigation (insight.py:451) validates the entry context (subject and purpose are both mandatory — "an Insight cannot exist without a subject", insight.py:510), writes the root via InsightRoot().update(uid=ins_…), emits an entry_intent_set InsightEvent, and sets heads.main. If the investigation is signal-driven it auto-links the trigger Signal and advances it to investigating. It deduplicates: for the same trigger Signal it returns the existing non-archived Insight unless force_new is passed (insight.py:528).
  2. Mutate — every block/edition/signal/task command that touches this Insight appends an InsightEvent and advances the head via the shared _append_event / _update_head helpers (_base.py:121). Direct status and link changes go through set_insight_status and link_signal. All writes are filtered through _build_insight_update against the _INSIGHT_WRITABLE_FIELDS allowlist (insight.py:153) so REST cannot clobber system-managed fields.
  3. Query — REST GET /insight_root/{uid} for the root, GET /insight/{uid}/timeline for the replayed event ledger.

Data shape

Required (des-objects.yml:174): schema_version (= 2), title, status, heads, entry_context, created_by. entry_context itself requires mode (signal_driven | curiosity_driven | task_driven | decision_driven), trigger (type + id), subject_ref (type + id), and purpose (purpose_type).

Key optional / computed: heads (branch → event id), pinned_block_ids[], edition_ids[], linked_signal_ids[], subject (denormalized for display), narrative / current_narrative, rationales[], comments[], revision_history[], ai_usage, visibility_context. Full list at intelligence.py:247.

Storage: the intelligence ES index under the insight_root alias; key queryable fields are insight_id, status, and entry_context.trigger.id. Every write passes through with_visibility() and carries a uds.visibility marking.

Invariants

  • Append-only ledger. Events are never updated or deleted; heads only move forward.
  • Subject and purpose are mandatory at creation — an Insight always has something it is about and a reason it exists.
  • Forward-only status with archived terminal; the closure gates (signals resolved, ≥1 edition, no open tasks) hold before published/archived.
  • One canonical investigation per trigger Signal — signal-driven creation deduplicates unless forced.
  • product.block — an Insight owns Blocks via pinned_block_ids[]; Block carries the insight_id back-reference.
  • product.edition — an Insight releases Editions (edition_ids[]); an Edition is a sealed snapshot of this Insight at a head event.
  • product.signal — Signals link bidirectionally (linked_signal_ids[] ↔ Signal linked_insight_ids[]); a signal-driven Insight names its trigger Signal in entry_context.trigger.
  • product.task — Tasks reference the Insight (task.insight_id) and emit events into its ledger.
  • product.investigation — "Investigation" is Beacon's user-facing name for this same object; that spec covers the Beacon ↔ cortex journey.

Open questions

  • Status vocabulary ([Q1]). Three sets compete: DES's 5 (draft/in_review/approved/published/archived, what the code enforces), intelligence.yaml's wider 8 (adds open/in_progress/closed), and Beacon's 3-state view. The golden set is DES's 5; the schema and Beacon need to converge.
  • Insight vs InsightRoot. The legacy insight alias object still exists and InsightRoot.insight_ids[] implies a root could group several. Whether the legacy object is dead or has a real consumer is unresolved.
  • Subject field shape. DES uses subject_ref / display_name; cortex must emit subject / name for Beacon. The rename is flagged in the DES changelog but not yet reconciled in the schema.

Realized by: component.cortex.intelligence