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).
- Create —
start_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 viaInsightRoot().update(uid=ins_…), emits anentry_intent_setInsightEvent, and setsheads.main. If the investigation is signal-driven it auto-links the trigger Signal and advances it toinvestigating. It deduplicates: for the same trigger Signal it returns the existing non-archived Insight unlessforce_newis passed (insight.py:528). - Mutate — every block/edition/signal/task command that touches this Insight appends an InsightEvent and advances
the head via the shared
_append_event/_update_headhelpers (_base.py:121). Direct status and link changes go throughset_insight_statusandlink_signal. All writes are filtered through_build_insight_updateagainst the_INSIGHT_WRITABLE_FIELDSallowlist (insight.py:153) so REST cannot clobber system-managed fields. - Query — REST
GET /insight_root/{uid}for the root,GET /insight/{uid}/timelinefor 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
archivedterminal; the closure gates (signals resolved, ≥1 edition, no open tasks) hold beforepublished/archived. - One canonical investigation per trigger Signal — signal-driven creation deduplicates unless forced.
Related products
product.block— an Insight owns Blocks viapinned_block_ids[]; Block carries theinsight_idback-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[]↔ Signallinked_insight_ids[]); a signal-driven Insight names its trigger Signal inentry_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 (addsopen/in_progress/closed), and Beacon's 3-state view. The golden set is DES's 5; the schema and Beacon need to converge. InsightvsInsightRoot. The legacyinsightalias object still exists andInsightRoot.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 emitsubject/namefor Beacon. The rename is flagged in the DES changelog but not yet reconciled in the schema.
Realized by: component.cortex.intelligence