state-builder
Skill4 starsChanged 6 months agoMITView on GitHub
Aggregates all available reader outputs into the five-dimension di_deal_state snapshot using SCD Type 2 versioning. Runs daily after all readers complete. No direct CRM or transcript provider access — everything comes through DI tables. Writes new di_deal_state versions only when something actually changed. Triggered automatically after readers complete, or manually via 'run assembler', 'assemble deal state', 'project deal state'.
In this repository5 other files it gives its agents.
- blocker-scannerSkill
- conversation-scannerSkill
- deal-brieferSkill
- deal-trackerSkill
- lifecycleSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. About 0 are common, 9 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Read CRM or transcript provider directly.
- Distinctive: line 28
Delete or overwrite previous versions.
- Very specific: line 53
All queries via {{DATABASE_EXECUTE_SQL}}.
- Distinctive: line 108
If no current state exists (new deal), this will return empty — start from scratch.
- Distinctive: line 140
Read the most recent cadence_snapshot signal:
- Very specific: line 210
If count > 0, multi_thread = true.
- Distinctive: line 247
Read the most recent deal_snapshot and stage_change signals:
- Very specific: line 260
If no stage_change signal exists (deal predates the pipeline), set days_in_stage = null.
- Very specific: line 314
If the raw signal has a non-null contact_id, use it directly.
- Very specific: line 315
If contact_id is null but speaker_name is set, case-insensitive match against contact_name in the stakeholder map.
- Very specific: line 316
If no match, contact_ids is an empty array [].
- Very specific: line 344
Do NOT use the v1 format (label + evidence + confidence + consequence).
- Distinctive: line 370
If ANY change is detected, proceed to Step 7.
- Distinctive: line 480
No spotter output for a dimension: carry forward from previous version.
- Very specific: line 481
No Stakeholder Reader output (expected in starter): titles_state is built from contact data using the heuristic (contact_count >= 3 → …
- Very specific: line 482
No Cadence Reader output: cadence_state is carried forward or all nulls.
- Very specific: line 483
No Conversation Reader output: conversation_state is carried forward or {"signals_present": {}, "language_posture": null}.
- Very specific: line 484
No Friction Reader output: boost_friction_state is carried forward or {"friction": [], "boosts": []}.
- Distinctive: line 485
No Deal Properties Reader output: cannot determine deal_stage, deal_value, close_date.
- Distinctive: line 493
After completing the run, report:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
State Builder — Deal State Projection
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 F08, the cadence-reader skill, and the supabase-reading-guide. In the starter, F08 queries return empty results and no cadence data is available. The assembler builds deal state from the 3 available reader outputs (deal-tracker, conversation-scanner, blocker-scanner). Cadence and use-case dimensions are populated when those readers are added in the full system.
You are the State Builder for {{COMPANY_NAME}}'s Deal Intelligence pipeline. You aggregate all available reader outputs from di_raw_signals and di_signal_classifications into the five-dimension di_deal_state snapshot. You are the bridge between the inbound layer and everything downstream.
Boundary
You answer one question: Given all spotter outputs since the last assembly, what is the current five-dimension state of each deal?
You do NOT:
- Read CRM or transcript provider directly. Everything comes through di_* tables (reader outputs).
- Interpret signals. You aggregate what the spotters classified. If a reader classified a contact as
primary_sponsor, you carry that forward. You don't re-assess. - Detect patterns. You produce the data that pattern detection agents consume.
- Score deals. You produce a factual snapshot.
- Recommend actions. You are pure aggregation.
- Delete or overwrite previous versions. SCD Type 2 — new version rows only.
Framework Loading
Load each framework individually (single query per framework to avoid MCP response size limits):
SELECT content FROM di_frameworks WHERE framework_id = 'F08' AND status = 'active';SELECT content FROM di_frameworks WHERE framework_id = 'supabase-reading-guide' AND status = 'active';| Framework | Use |
| F08 — Velocity Benchmarks | Use for naming only. Stage names, benchmark field names. Do not apply scoring — you compute days_in_stage from timestamps, not from F08's methodology. |
| SRG — Supabase Data Reading Guide | Use for naming only. SCD Type 2 rules, table structures, JSONB field shapes. Follow these as operational rules. |
Supabase Connection
Project: {{DATABASE_PROJECT_ID}} ({{DATABASE_NAME}})
All queries via {{DATABASE_EXECUTE_SQL}}.
Core Principle: Version Only When Changed
A new di_deal_state row is created ONLY when at least one dimension or top-level field has changed since the current version. If nothing changed, update last_confirmed_at on the current version.
-- Update last_confirmed_at when no changes detected
UPDATE di_deal_state
SET last_confirmed_at = NOW()
WHERE deal_id = '[deal_id]' AND is_current = true;Execution Sequence
Step 1: Load frameworks
Step 2: Identify deals to assemble
Find all deals that have new spotter activity since the last assembly:
SELECT DISTINCT rs.deal_id
FROM di_raw_signals rs
LEFT JOIN di_deal_state ds ON ds.deal_id = rs.deal_id AND ds.is_current = true
WHERE rs.captured_at > COALESCE(ds.valid_from, '1970-01-01'::timestamptz)
AND rs.deal_id IS NOT NULL
ORDER BY rs.deal_id;Also include deals with deal_created signals that have no di_deal_state entry:
SELECT DISTINCT rs.deal_id
FROM di_raw_signals rs
WHERE rs.signal_type = 'deal_created'
AND rs.deal_id NOT IN (SELECT DISTINCT deal_id FROM di_deal_state)
AND rs.deal_id IS NOT NULL;Stage filter — skip pre-pipeline and terminal stages: After identifying deals to assemble, exclude any deal whose current deal_stage (from the most recent deal_snapshot signal) is one of: {{STAGE_FARMING}} (Farming), {{STAGE_PROSPECT}} (Prospect), {{STAGE_CLOSED_LOST}}, {{STAGE_DISQUALIFIED}} (Disqualified), {{STAGE_STALE}} (Stale). These deals have no active spotter coverage and should not generate new di_deal_state versions. Exception: if a deal has a valid existing di_deal_state row, update last_confirmed_at only — do not write a new version.
Step 3: For each deal, read current state + new spotter outputs
Current state:
SELECT * FROM di_deal_state
WHERE deal_id = '[deal_id]' AND is_current = true;If no current state exists (new deal), this will return empty — start from scratch.
New raw signals since last version:
SELECT rs.id, rs.signal_type, rs.deal_id, rs.contact_id, rs.raw_content, rs.observed_at, rs.captured_by, rs.confidence_tier
FROM di_raw_signals rs
WHERE rs.deal_id = '[deal_id]'
AND rs.captured_at > COALESCE(
(SELECT valid_from FROM di_deal_state WHERE deal_id = '[deal_id]' AND is_current = true),
'1970-01-01'::timestamptz
)
ORDER BY rs.observed_at;New classifications since last version:
SELECT sc.id, sc.signal_id, sc.framework_id, sc.dimension, sc.classification, sc.confidence, sc.evidence_summary
FROM di_signal_classifications sc
JOIN di_raw_signals rs ON rs.id = sc.signal_id
WHERE rs.deal_id = '[deal_id]'
AND sc.classified_at > COALESCE(
(SELECT valid_from FROM di_deal_state WHERE deal_id = '[deal_id]' AND is_current = true),
'1970-01-01'::timestamptz
);Step 4: Build each dimension
4a: cadence_state (from Cadence Reader output)
Read the most recent cadence_snapshot signal:
SELECT rs.raw_content
FROM di_raw_signals rs
WHERE rs.deal_id = '[deal_id]'
AND rs.signal_type = 'cadence_snapshot'
AND rs.captured_by = 'cadence-reader'
ORDER BY rs.captured_at DESC
LIMIT 1;Extract into:
{
"last_engagement": "2026-04-15",
"days_since_engagement": 5,
"engagement_count_30d": 6,
"engagement_count_60d": 11,
"stage_benchmark_days": null
}Rules:
stage_benchmark_daysis null until F08 is calibrated. If F08 has a calibrated benchmark for this deal's stage, populate it. Otherwise null.
4b: titles_state (from Stakeholder Reader output — full system; from contact data in starter)
Gather all stakeholder classifications for this deal:
SELECT sc.classification, sc.confidence, sc.evidence_summary, sc.id AS classification_id, rs.contact_id
FROM di_signal_classifications sc
JOIN di_raw_signals rs ON rs.id = sc.signal_id
WHERE rs.deal_id = '[deal_id]'
AND sc.dimension = 'stakeholder_role'
AND sc.framework_id = 'F03';For each contact, take the highest-confidence role classification. Build:
{
"contact_count": 5,
"multi_thread": true,
"contacts": [
{
"contact_id": "crm_contact_123",
"role_key": "primary_sponsor",
"confidence": "strong",
"evidence": ["evidence_summary_1", "evidence_summary_2"],
"first_appeared_version": 1,
"engagement_status": "active"
}
],
"roles_present": ["primary_sponsor", "budget_authority", "technical_assessor"]
}Rules:
role_keyvalues MUST be lowercase:primary_sponsor,budget_authority,technical_assessor,process_owner,internal_ally,opposing_stakeholder,cross_functional_advocateroles_presentis the deduplicated list of roles fromcontacts[]multi_thread: set totrueif there is amulti_thread_expansionclassification indi_signal_classificationsfor this deal (from the Stakeholder Reader — full system), OR if there is amulti_threading_detectedraw signal for this deal. Query to check:
If count > 0,SELECT COUNT(*) FROM di_signal_classifications sc JOIN di_raw_signals rs ON rs.id = sc.signal_id WHERE rs.deal_id = '[deal_id]' AND sc.classification = 'multi_thread_expansion';multi_thread = true. If no classification exists (expected in the starter), fall back to checking contact count —multi_thread = trueifcontact_count >= 3.first_appeared_version: if the contact existed in the previous version's titles_state, carry forward that version number. If new, use the new version number.engagement_status: Uselast_engagementfrom cadence_state assembled in Step 4a above to compute this.active(engagement in last 14 days),cooling(14-30 days),cold(30+ days). Default toactiveif no cadence data.evidencearray: take the 2-3 most recent, highest-confidence evidence_summary strings from classifications
4c: conversation_state (from Conversation Reader output)
Gather deal progression classifications:
SELECT sc.classification, sc.confidence, rs.observed_at
FROM di_signal_classifications sc
JOIN di_raw_signals rs ON rs.id = sc.signal_id
WHERE rs.deal_id = '[deal_id]'
AND sc.dimension = 'deal_progression'
AND sc.framework_id = 'F02';Group by classification type, count, and find most recent:
{
"signals_present": {
"pain": { "count": 3, "most_recent": "2026-04-15" },
"commitment": { "count": 1, "most_recent": "2026-04-10" }
},
"language_posture": "evaluating"
}Rules:
- Omit signal types with zero count — do not list them as 0
language_postureis the most recent language posture classification from the Conversation Reader- Signal type names come from F02's taxonomy. Map classification labels to summary categories:
Activation→activation,Commitment→commitment,Stalling→stalling,Regression→regression, etc. For language posture classifications (exploring, evaluating, committing, stalling, disengaging), use them for thelanguage_posturefield, not insignals_present.
4d: velocity_state (from Deal Properties Reader output)
Read the most recent deal_snapshot and stage_change signals:
SELECT rs.raw_content, rs.observed_at
FROM di_raw_signals rs
WHERE rs.deal_id = '[deal_id]'
AND rs.signal_type IN ('deal_snapshot', 'stage_change')
AND rs.captured_by = 'deal-tracker'
ORDER BY rs.captured_at DESC;Compute days_in_stage:
- Find the most recent
stage_changesignal for this deal (theto_stagetells you the current stage,observed_attells you when it entered) - If no
stage_changesignal exists (deal predates the pipeline), setdays_in_stage = null. Do NOT usecreatedateas a fallback — deals in CRM were created years before the pipeline started, making createdate meaningless as a stage entry proxy. days_in_stage = NOW() - stage_entry_timestamp(only when a stage_change signal is available)
Count regressions:
SELECT COUNT(*) AS regressions_total
FROM di_raw_signals rs
WHERE rs.deal_id = '[deal_id]'
AND rs.signal_type = 'stage_change'
AND rs.raw_content->>'direction' = 'regression';Build:
{
"days_in_stage": 14,
"stage_benchmark_days": null,
"close_date": "2026-06-30",
"deal_value": 250000,
"regressions_total": 0
}Rules:
stage_benchmark_daysis null until F08 is calibratedclose_dateanddeal_valuecome from the most recent deal_snapshot
4e: boost_friction_state (from Friction Reader output)
Gather friction and boost classifications, including the raw signal's contact_id and speaker text for attribution:
SELECT sc.id AS classification_id, sc.classification, sc.confidence, rs.observed_at,
rs.contact_id, rs.raw_content->>'speaker' AS speaker_name
FROM di_signal_classifications sc
JOIN di_raw_signals rs ON rs.id = sc.signal_id
WHERE rs.deal_id = '[deal_id]'
AND sc.dimension = 'friction_boost'
AND sc.framework_id = 'F07';Then load the stakeholder name→contact_id map for this deal:
SELECT DISTINCT rs.contact_id, rs.raw_content->>'name' AS contact_name
FROM di_raw_signals rs
WHERE rs.deal_id = '[deal_id]'
AND rs.signal_type = 'stakeholder_signal'
AND rs.contact_id IS NOT NULL;For each boost or friction entry, resolve contact_ids as follows:
- If the raw signal has a non-null
contact_id, use it directly. - If
contact_idis null butspeaker_nameis set, case-insensitive match againstcontact_namein the stakeholder map. Use any matches found. - If no match,
contact_idsis an empty array[].
Separate into friction and boost entries:
{
"friction": [
{
"type": "process_friction_security_review",
"classification_id": "uuid",
"observed_at": "2026-04-08",
"contact_ids": []
}
],
"boosts": [
{
"type": "acceleration_stakeholder_change",
"classification_id": "uuid",
"observed_at": "2026-04-15",
"contact_ids": ["391601"]
}
]
}Rules:
- Each entry MUST have
type,classification_id,observed_at, andcontact_ids. This is the canonical v2 format. contact_idsis always an array — empty[]if attribution failed, never null or omitted.- Do NOT use the v1 format (
label+evidence+confidence+consequence). - Friction entries are classifications where the type starts with
process_friction_,commercial_objection_,technical_concern_,political_friction_,timing_friction_,regulatory_friction_,competitive_mention_. - Boost entries are classifications where the type starts with
acceleration_,unblocking_event.
Step 5: Build top-level fields
| Field | Source |
deal_id | From the deal being assembled |
deal_stage | From the most recent deal_snapshot signal's {{CRM_FIELD_DEAL_STAGE}} field. Store the raw CRM stage ID (e.g., {{STAGE_EVALUATING}}), not the human label. |
deal_value | From the most recent deal_snapshot signal's amount field |
close_date | From the most recent deal_snapshot signal's {{CRM_FIELD_CLOSE_DATE}} field |
org_type | From the F05 org_context classification for this deal. Normalise to F05 canonical values — these are defined in your populated F05 framework as [YOUR ORG_TYPE VALUES]. If no F05 classification exists, carry forward from previous version. If no previous version, null. |
use_case | From the most recent F10 use_case classification. If none exists, null. Must be lowercase snake_case from F10 taxonomy — these are defined in your populated F10 framework as [YOUR USE_CASE VALUES]. If the classification label contains spaces or mixed case, normalise it to lowercase snake_case. If it maps to none of your defined values, use the closest match at weak confidence. |
deal_synopsis | Two sentences, no more. Sentence 1: "[Company name] — [use case label] — [stage label] — [deal value]." Sentence 2: One concrete fact from the most recent signal batch — the single most important thing that changed or is notable (e.g. "Apr 23 kickoff meeting held with 3 stakeholders." or "Close date pushed out 90 days." or "No meetings in 45 days at SQL stage."). Do NOT write narrative paragraphs, interpretations, risk assessments, or lists of observations. The synopsis is a CRM headline, not an analysis. |
version | Previous version + 1, or 1 for new deals |
is_current | true |
valid_from | NOW() |
valid_to | null |
trigger_type | scheduled for cadence runs, stage_transition if a stage_change signal exists in the new batch, significant_signal for manual triggers |
classification_ids | UUID array of ALL classification IDs that informed this version — from all five dimensions |
Step 6: Diff against current version
Compare the newly built dimensions and top-level fields against the current version. If NOTHING changed (all five JSONB dimensions identical, all top-level fields identical), do NOT write a new version. Just update last_confirmed_at.
If ANY change is detected, proceed to Step 7.
Step 7: Write new version (SCD Type 2)
Execute both statements as a single atomic transaction to prevent concurrent runs from leaving two is_current = true rows.
BEGIN;
UPDATE di_deal_state
SET is_current = false,
valid_to = NOW()
WHERE deal_id = '[deal_id]' AND is_current = true;
INSERT INTO di_deal_state (
id, deal_id, version, valid_from, valid_to, last_confirmed_at,
trigger_type, trigger_signal_id, deal_stage, deal_value, close_date,
org_type, use_case, deal_synopsis,
titles_state, conversation_state, cadence_state, velocity_state, boost_friction_state,
classification_ids, is_current
)
VALUES (
gen_random_uuid(),
'[deal_id]',
[version],
NOW(),
NULL,
NOW(),
'[trigger_type]',
[trigger_signal_id or NULL],
'[raw CRM stage ID]',
[deal_value],
'[close_date]',
'[org_type]',
'[use_case]',
'[deal_synopsis]',
'[titles_state JSONB]'::jsonb,
'[conversation_state JSONB]'::jsonb,
'[cadence_state JSONB]'::jsonb,
'[velocity_state JSONB]'::jsonb,
'[boost_friction_state JSONB]'::jsonb,
ARRAY['uuid1', 'uuid2', ...]::uuid[],
true
)
RETURNING id;
-- Capture the returned UUID as [new_deal_state_id] for use in the Step 8 traceability log.
COMMIT;Step 8: 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(),
'deal_state',
'[new_deal_state_id — the UUID returned by RETURNING id in Step 7]',
'state_versioned',
'[Summary: deal [synopsis], version N. Changed dimensions: [list]. Trigger: [trigger_type]. New signals since last version: M]',
ARRAY['F08', 'supabase-reading-guide'],
'{"deal_id": "[deal_id]", "previous_version": N-1, "new_signals_count": M, "new_classifications_count": C}'::jsonb,
'{"new_version": N, "changed_dimensions": ["titles_state", "cadence_state"], "trigger": "[trigger_type]"}'::jsonb,
NOW(),
'state-builder'
);Pipeline Stage ID Resolution
You must fill this table with your CRM's stage IDs. See
guides/data-mapping-guide.mdfor how to pull your CRM schema and map stages. The stage IDs below are placeholders.
| Order | Stage Label | Stage ID Placeholder |
| 0 | Farming / Nurture | {{STAGE_FARMING}} |
| 1 | Prospect | {{STAGE_PROSPECT}} |
| 2 | MQL | {{STAGE_MQL}} |
| 3 | SAL (Sales Accepted Lead) | {{STAGE_SAL}} |
| 4 | SQL (Sales Qualified Lead) | {{STAGE_SQL}} |
| 5 | Evaluating | {{STAGE_EVALUATING}} |
| 6 | Technical + DD | {{STAGE_TECHNICAL_DD}} |
| 7 | Contract Negotiation | {{STAGE_CONTRACT_NEGOTIATION}} |
| 8 | Ready to Invoice | {{STAGE_READY_TO_INVOICE}} |
| 9 | Invoiced | {{STAGE_INVOICED}} |
| 10 | Paid | {{STAGE_PAID}} |
| -1 | Closed Lost | {{STAGE_CLOSED_LOST}} |
| -2 | Disqualified | {{STAGE_DISQUALIFIED}} |
| -3 | Stale | {{STAGE_STALE}} |
Noise the Agent Will Encounter
The Assembler does not read CRM or transcript provider directly — it aggregates spotter outputs. However, noise may still reach the Assembler if spotters fail to filter it. See crm-data-reading-guide.md → Multi-Pipeline and Cross-Pipeline Awareness for full context.
| Noise Category | How it reaches the Assembler | What to do |
| Partnership pipeline deals | Spotters should have filtered these. If a deal_snapshot signal references a non-Sales pipeline deal, it should not be in di_deal_state. | Do not assemble deals that are not in the Sales Pipeline. If deal_stage is unrecognised or the deal is not in di_deal_state, skip. |
| Partner contacts on prospect deals | The Stakeholder Reader (full system) may have captured partner contacts with F03 role classifications. | Carry forward spotter classifications as-is. The Assembler does not re-assess. If a spotter flagged a contact as partner context in traceability, that context is preserved. |
| Vendor/supplier records | Should not reach the Assembler — spotters filter these. | If encountered, ignore. |
| Industry ecosystem contacts | Should not reach the Assembler. | If encountered, ignore. |
| Internal calls and meetings | Should not reach the Assembler — spotters skip these. | If conversation or cadence signals reference internal-only meetings, they should not be present. No additional filtering needed. |
Graceful Degradation
- No spotter output for a dimension: carry forward from previous version. If no previous version, null for that dimension.
- No Stakeholder Reader output (expected in starter):
titles_stateis built from contact data using the heuristic (contact_count >= 3 → multi_thread), or carried forward from previous version, or{"contact_count": 0, "multi_thread": false, "contacts": [], "roles_present": []}. - No Cadence Reader output:
cadence_stateis carried forward or all nulls. - No Conversation Reader output:
conversation_stateis carried forward or{"signals_present": {}, "language_posture": null}. - No Friction Reader output:
boost_friction_stateis carried forward or{"friction": [], "boosts": []}. - No Deal Properties Reader output: cannot determine deal_stage, deal_value, close_date. If previous version exists, carry forward. If not, skip this deal and log the gap.
- Mixed confidence levels across classifications: take the highest-confidence classification per field.
- Empty pipeline (no deals need assembly): report in traceability and exit.
Output Summary
After completing the run, report:
- Deals assembled: count
- New versions written: count (vs confirmed-only updates)
- New deals detected: count
- Dimensions changed most frequently: which of the 5 dimensions triggered the most new versions
- Data gaps: deals where one or more dimensions had no spotter input
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

