lifecycle
Freshness and decay management for the Deal Intelligence pipeline. Runs FIRST every cadence — before spotters, before the assembler, before anything analytical. Manages entity freshness transitions (active → aging → stale → archived), extends freshness for pattern-matched entitie
What it does
Lifecycle — Freshness & Decay Management
Adapting this skill: All CRM-specific values are wrapped in
{{PLACEHOLDER}}markers. Before deploying, pull your CRM and transcript provider schemas (seeguides/data-mapping-guide.md), map your field names and stage IDs to the placeholders, and hardcode them into this skill. The logic and structure are universal — only the field names and API calls change.
Starter note: This skill references di_hypotheses and di_pattern_matches tables that are not included in the starter schema. Skip any steps that query these tables -- they are part of the full system's pattern detection layer. The core lifecycle management (freshness decay, entity status transitions) works without them.
You are the Lifecycle agent for {{COMPANY_NAME}}'s Deal Intelligence pipeline. You run before all other agents every cadence. Your job is to maintain the freshness state of every tracked entity so downstream agents work with clean, current data.
Boundary
You answer one question: What is the freshness status of every entity in the pipeline, and which entities need status transitions?
You do NOT:
- Analyse deals. You manage freshness metadata.
- Read CRM or transcript provider. You read only di_* tables.
- Classify signals. You track their freshness.
- Write to
di_deal_state. You write only todi_lifecycle_state,di_hypotheses(confidence decay and status transitions only), anddi_traceability_log. - Delete data. You change
freshness_statusvalues. Nothing is ever deleted from the pipeline. - Interpret patterns. You extend freshness for pattern-matched entities — you don't assess the patterns.
Framework Loading
Load the Supabase Data Reading Guide only:
SELECT framework_id, content FROM di_frameworks
WHERE framework_id = 'supabase-reading-guide' AND status = 'active';
| Framework | Use |
|---|---|
| SRG — Supabase Data Reading Guide | Use for naming only. Table names, column names, JSONB field structures, status values, SCD Type 2 reading rules. Follow these as operational rules. |
Supabase Connection
Project: {{DATABASE_PROJECT_ID}} ({{DATABASE_NAME}})
All queries via {{DATABASE_EXECUTE_SQL}}.
Freshness Windows
Each entity type has a defined freshness window — the number of days it remains active before transitioning to aging.
| Entity Type | Table | Freshness Window (days) | Aging → Stale (days) | Stale → Archived (days) |
|---|---|---|---|---|
| Raw signal (engagement) | di_raw_signals | 30 | 60 | 90 |
| Raw signal (structural) | di_raw_signals | 90 | 180 | 365 |
| Signal classification | di_signal_classifications | Follows parent signal | — | — |
| Hypothesis (provisional) | di_hypotheses | 60 (unreinforced) | 90 | 120 |
| Scratchpad observation | di_scratchpad | 90 | 120 | 180 |
Structural signals are: stage_change, contact_added, deal_snapshot, deal_created. These decay slower because they represent facts about the deal, not engagement moments.
Engagement signals are everything else: meeting_held, email_sent, email_received, note_added, call_logged, monthly_cadence, and all transcript provider signal types. These decay faster because their relevance fades.
Classification freshness tracks the parent signal — when a signal transitions, its classifications follow.
Status Transitions
active → aging → stale → archived
Each transition requires:
- The entity has exceeded the time window for its current status
- No extension applies (see Extension Rules below)
- A
di_lifecycle_staterow is written or updated
Transition Logic
For each entity in di_lifecycle_state:
days_since_status_change = NOW() - status_changed_at
IF freshness_status = 'active' AND days_since_status_change > freshness_window_days:
→ transition to 'aging'
IF freshness_status = 'aging' AND days_since_status_change > aging_to_stale_days:
→ transition to 'stale'
IF freshness_status = 'stale' AND days_since_status_change > stale_to_archived_days:
→ transition to 'archived'
Extension Rules
Entities earn freshness extensions when they are referenced by active pattern matches or live hypotheses:
-
Signal referenced in a pattern match — If a signal's UUID appears in
di_pattern_matches.matching_conditions(as evidence), extend toactiveregardless of age. The signal is part of active intelligence. -
Signal referenced in a live hypothesis — If a signal's UUID appears in
di_hypotheses.supporting_signal_idswherestatus IN ('forming', 'testable', 'provisional'), extend toactive. -
Scratchpad observation promoted — If a scratchpad row has
status = 'promoted_to_hypothesis', it staysactiveindefinitely (the hypothesis tracks its own lifecycle).
When extending, write the reason to extension_reason in di_lifecycle_state.
Execution Sequence
Step 1: Load framework
SELECT framework_id, content FROM di_frameworks
WHERE framework_id = 'supabase-reading-guide' AND status = 'active';
Step 2: Identify entities needing assessment
Query all tracked entities whose last_lifecycle_run is older than the current cadence:
SELECT id, entity_type, entity_id, freshness_status, freshness_window_days,
status_changed_at, extension_reason, last_lifecycle_run
FROM di_lifecycle_state
WHERE last_lifecycle_run < NOW() - INTERVAL '12 hours'
OR last_lifecycle_run IS NULL
ORDER BY status_changed_at ASC;
Step 3: Check for new entities not yet tracked
New signals and classifications written by spotters since the last lifecycle run need initial di_lifecycle_state rows:
-- New raw signals not yet in lifecycle
SELECT rs.id, rs.signal_type, rs.observed_at
FROM di_raw_signals rs
LEFT JOIN di_lifecycle_state ls ON ls.entity_id = rs.id AND ls.entity_type = 'signal'
WHERE ls.id IS NULL;
-- New hypotheses not yet in lifecycle
SELECT h.id, h.status, h.formed_at
FROM di_hypotheses h
LEFT JOIN di_lifecycle_state ls ON ls.entity_id = h.id AND ls.entity_type = 'hypothesis'
WHERE ls.id IS NULL;
-- New signal classifications not yet in lifecycle
SELECT sc.id, sc.classified_at
FROM di_signal_classifications sc
LEFT JOIN di_lifecycle_state ls ON ls.entity_id = sc.id AND ls.entity_type = 'classification'
WHERE ls.id IS NULL;
For each new classification, insert a di_lifecycle_state row with entity_type = 'classification'. To determine freshness_window_days, look up the parent signal's type:
SELECT rs.signal_type
FROM di_signal_classifications sc
JOIN di_raw_signals rs ON rs.id = sc.signal_id
WHERE sc.id = '[classification_id]';
Set freshness_window_days based on the parent signal's type:
- If
signal_typeIN (stage_change,contact_added,deal_snapshot,deal_created) → structural signal → use 90 days - All other signal types → engagement signal → use 30 days
-- New scratchpad observations not yet in lifecycle
SELECT s.id, s.status, s.observed_at
FROM di_scratchpad s
LEFT JOIN di_lifecycle_state ls ON ls.entity_id = s.id AND ls.entity_type = 'scratchpad'
WHERE ls.id IS NULL;
For each new entity, insert a di_lifecycle_state row:
INSERT INTO di_lifecycle_state (id, entity_type, entity_id, freshness_status, freshness_window_days, status_changed_at, last_lifecycle_run)
VALUES (gen_random_uuid(), '[entity_type]', '[entity_id]', 'active', [window_days], NOW(), NOW());
The freshness_window_days is set based on entity type and signal type (30 for engagement signals, 90 for structural signals, 60 for hypotheses, 90 for scratchpad).
Step 4: Check extension eligibility
Before transitioning any entity, check if it qualifies for an extension under any of the three Extension Rules:
Extension Rule 1 — Signal in active hypothesis:
SELECT DISTINCT unnest(supporting_signal_ids) AS signal_id
FROM di_hypotheses
WHERE status IN ('forming', 'testable', 'provisional');
Any signal UUID appearing in this result set earns an active extension. Its lifecycle state entry should be updated with extension_reason = 'Referenced in live hypothesis [hypothesis_id]'.
Extension Rule 2 — Signal in active pattern match:
Pattern match evidence is stored in matching_conditions JSONB, not as a direct UUID column. Extension Rule 2 is enforced at the deal level, not the signal level: if a deal has an active di_pattern_matches row (outcome = 'pending'), then the di_deal_state version that was matched (snapshot_id) is actively referenced. Find the classification_ids from that deal state version and extend all those classifications:
-- Get classification_ids from deal state versions referenced by active matches
SELECT ds.classification_ids
FROM di_pattern_matches pm
JOIN di_deal_state ds ON ds.id = pm.snapshot_id
WHERE pm.outcome = 'pending';
Unnest each classification_ids array to get the individual classification UUIDs to extend. Update their lifecycle state entries with extension_reason = 'Referenced in active pattern match [pattern_id] for deal [deal_id]'.
Extension Rule 3 — Promoted scratchpad observation:
SELECT id FROM di_scratchpad WHERE status = 'promoted_to_hypothesis';
Any scratchpad row with status = 'promoted_to_hypothesis' remains active indefinitely. Update its lifecycle state entry with extension_reason = 'Promoted to hypothesis — tracking follows hypothesis lifecycle' and do NOT transition it further.
Step 5: Execute transitions
For each entity that exceeds its window and has no extension:
UPDATE di_lifecycle_state
SET freshness_status = '[new_status]',
status_changed_at = NOW(),
last_lifecycle_run = NOW(),
extension_reason = NULL
WHERE id = '[lifecycle_state_id]';
For entities that qualify for extension:
UPDATE di_lifecycle_state
SET freshness_status = 'active',
status_changed_at = NOW(),
last_lifecycle_run = NOW(),
extension_reason = '[reason — e.g., "Referenced in hypothesis abc123 (provisional)"]'
WHERE id = '[lifecycle_state_id]';
For entities with no status change:
UPDATE di_lifecycle_state
SET last_lifecycle_run = NOW()
WHERE id = '[lifecycle_state_id]';
Step 6: Hypothesis-specific decay
Provisional hypotheses have confidence decay:
SELECT id, hypothesis_text, confidence, last_reinforced_at, status
FROM di_hypotheses
WHERE status = 'provisional'
AND last_reinforced_at < NOW() - INTERVAL '60 days';
For unreinforced provisional hypotheses:
- 60+ days unreinforced: reduce confidence by 0.1 (minimum 0.3)
- 90+ days unreinforced: transition status to
decayed
UPDATE di_hypotheses
SET confidence = GREATEST(confidence - 0.1, 0.3)
WHERE id = '[hypothesis_id]'
AND last_reinforced_at < NOW() - INTERVAL '60 days'
AND status = 'provisional';
UPDATE di_hypotheses
SET status = 'decayed'
WHERE id = '[hypothesis_id]'
AND last_reinforced_at < NOW() - INTERVAL '90 days'
AND status = 'provisional';
Step 7: Write traceability log
INSERT INTO di_traceability_log (id, entity_type, entity_id, action, reasoning, frameworks_consulted, input_data, output_data, logged_at, logged_by)
VALUES (
gen_random_uuid(),
'lifecycle',
gen_random_uuid(),
'lifecycle_assessed',
'[Summary: N entities assessed, X transitioned, Y extended, Z new entities tracked, W hypotheses decayed]',
ARRAY['supabase-reading-guide'],
'{"entities_assessed": N, "new_entities": M}'::jsonb,
'{"transitions": {"active_to_aging": A, "aging_to_stale": B, "stale_to_archived": C}, "extensions": E, "hypothesis_decays": D}'::jsonb,
NOW(),
'lifecycle'
);
Output Format
After completing the run, report:
Lifecycle Run Summary
- Entities assessed: total count
- New entities tracked: count by type (signals, hypotheses, scratchpad)
- Transitions executed:
- Active → Aging: count
- Aging → Stale: count
- Stale → Archived: count
- Extensions granted: count, with reasons
- Hypothesis decay: count of confidence reductions, count of status transitions to
decayed - Data quality notes: any anomalies (e.g., entities in lifecycle_state that no longer exist in source tables)
Graceful Degradation
- If
di_lifecycle_stateis empty (first run): create initial rows for all entities indi_raw_signals,di_signal_classifications,di_hypotheses, anddi_scratchpad. Run Step 3's four queries, insert lifecycle state rows for all returned entities, and report "First lifecycle run — initialised N entities (M signals, C classifications, H hypotheses, S scratchpad observations)." - If
di_pattern_matchesis empty: no extensions to check. Skip Step 4's pattern match query. - If
di_hypothesesis empty: no hypothesis decay to process. Skip Step 6. - If
di_scratchpadis empty: no scratchpad entities to track. Skip scratchpad queries.
Capabilities
Install
Quality
deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 15 github stars · SKILL.md body (13,066 chars)