conversation_gaps
Identify extended pauses in iMessage conversations to analyze communication patterns, detect periods of reduced contact, and understand relationship dynamics through message history.
Instructions
Find the longest silences in a conversation. Detects periods where you and a contact stopped talking — falling-outs, busy periods, or drifting apart. Shows gap duration and when it happened.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| contact | Yes | Contact handle or name | |
| min_gap_days | No | Minimum gap in days to include (default: 7) | |
| limit | No | Max gaps to return (default 10) |
Implementation Reference
- src/tools/patterns.ts:390-456 (handler)Full registration and implementation of the conversation_gaps tool. This includes the handler function that queries the database to find the longest silences between consecutive messages in a conversation, using SQL window functions (LAG) to compare message timestamps and identify gaps exceeding a specified threshold.
server.tool( "conversation_gaps", "Find the longest silences in a conversation. Detects periods where you and a contact stopped talking — falling-outs, busy periods, or drifting apart. Shows gap duration and when it happened.", { contact: z.string().describe("Contact handle or name"), min_gap_days: z.number().optional().describe("Minimum gap in days to include (default: 7)"), limit: z.number().optional().describe("Max gaps to return (default 10)"), }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (params) => { const db = getDb(); const limit = clamp(params.limit ?? 10, 1, MAX_LIMIT); const minGapNano = (params.min_gap_days ?? 7) * 86400 * 1_000_000_000; const rows = db.prepare(` WITH msg_pairs AS ( SELECT ${DATE_EXPR} as date, LAG(${DATE_EXPR}) OVER (ORDER BY m.date) as prev_date, m.date as raw_date, LAG(m.date) OVER (ORDER BY m.date) as prev_raw_date, m.is_from_me as broken_by_me, m.text as break_text, m.attributedBody as break_body FROM message m JOIN handle h ON m.handle_id = h.ROWID WHERE h.id LIKE @contact AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL) ${MSG_FILTER} ) SELECT prev_date as silence_start, date as silence_end, ROUND((raw_date - prev_raw_date) / 1000000000.0 / 86400.0, 1) as gap_days, broken_by_me, break_text, break_body FROM msg_pairs WHERE prev_raw_date IS NOT NULL AND (raw_date - prev_raw_date) > @min_gap_nano ORDER BY gap_days DESC LIMIT @limit `).all({ contact: `%${params.contact}%`, min_gap_nano: minGapNano, limit }) as any[]; if (rows.length === 0) { return { content: [{ type: "text", text: `No gaps longer than ${params.min_gap_days ?? 7} days found for "${params.contact}"` }] }; } const contact = lookupContact(params.contact); return { content: [{ type: "text", text: JSON.stringify({ contact: contact.name, min_gap_days: params.min_gap_days ?? 7, gaps: rows.map((r: any) => ({ gap_days: r.gap_days, silence_start: r.silence_start, silence_end: r.silence_end, broken_by: r.broken_by_me ? "you" : contact.name, ice_breaker_text: safeText(getMessageText({ text: r.break_text, attributedBody: r.break_body })) || "(attachment)", })), }, null, 2), }], }; }, ); - src/tools/patterns.ts:394-397 (schema)Input schema definition for conversation_gaps tool using Zod validation. Defines three optional parameters: contact (required string), min_gap_days (default 7), and limit (default 10).
contact: z.string().describe("Contact handle or name"), min_gap_days: z.number().optional().describe("Minimum gap in days to include (default: 7)"), limit: z.number().optional().describe("Max gaps to return (default 10)"), },