# Fix: Parent Section Breakdown Awareness + Sequential Breakdown **Model:** fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo **Date:** 2026-03-31 **Parent Plan:** [1774872124705-glowing-river.md](./1774872124705-glowing-river.md) ## Summary This plan addresses two issues in the spec ingestion process: 1. **Parent Awareness**: Parent sections now contain references to their subsections 2. **Sequential + Recursive Breakdown**: Large sections are broken down by trying tags sequentially, and extracted subsections are recursively checked and broken down if still large **Key Features:** - Always extract `emu-clause` with titles first - Breakdown tags tried in order: `emu-table` → `emu-grammar` → `td` → `p` - Continue to next tag only if previous didn't reduce size enough - **Recursive check**: Each extracted subsection is also checked and can be further broken down - **Hierarchical IDs**: Show full path like `sec-if-statement-emu-table-1-td-2` - **Max depth**: 3 levels prevents excessive nesting - **Metadata tracking**: `subsections` array lists children (breakdown type derived from IDs) - **Parent references**: Agent can discover and fetch the full hierarchy ## Files to Modify | File | Changes | |------|---------| | `setup/ingest.ts` | Add parent chunk with subsection awareness + recursive breakdown | ## Implementation Details ### Breakdown Strategy **Phase 1: Extract emu-clause with titles** - Parse all `emu-clause` elements - Extract `id`, `title` (from `h1`), and content - Always keep these as primary structure **Phase 2: Recursive sequential breakdown** For each section (emu-clause or subsection) > 5000 chars: 1. Try `emu-table` - Extract tables as subsections 2. If still large, try `emu-grammar` - Extract grammar productions 3. If still large, try `td` - Break table cells 4. If still large, try `p` - Break paragraphs **Phase 3: Recursive check on extracted subsections** - Each extracted subsection is ALSO checked for size - If still > 5000 chars, recursively apply breakdown starting from the NEXT tag - Example: `sec-if-statement-emu-table-1` is 8000 chars → try `emu-grammar`, then `td`, then `p` **Stop condition:** All chunks (at any level) are < threshold ### Configuration ```typescript const LARGE_DOC_THRESHOLD = 5000; const BREAKDOWN_TAGS = ["emu-table", "emu-grammar", "td", "p"] as const; const MAX_RECURSION_DEPTH = 3; ``` ### Breakdown Function ```typescript interface BreakdownResult { parentDoc: { content: string; tagUsed: string | null; subsections: string[]; }; subsectionDocs: Document[]; } function breakDownSection( html: string, baseId: string, baseTitle: string, sourceFile: string, parentId: string | null, depth: number = 0 ): BreakdownResult { const $ = cheerio.load(`
${html}
`); const $section = $("div").first(); const fullText = $section.text().trim(); // Base case: small enough or max depth if (fullText.length <= LARGE_DOC_THRESHOLD || depth >= MAX_RECURSION_DEPTH) { return { parentDoc: { content: html, tagUsed: null, subsections: [] }, subsectionDocs: [] }; } // Try each breakdown tag sequentially const subsectionIds: string[] = []; const subsectionDocs: Document[] = []; let remainingHtml = html; let tagUsed: string | null = null; for (let i = 0; i < BREAKDOWN_TAGS.length; i++) { const tagName = BREAKDOWN_TAGS[i]; const $temp = cheerio.load(`
${remainingHtml}
`); const $tempSection = $("div").first(); // Check if this tag exists if ($tempSection.find(tagName).length === 0) { continue; } // Extract elements of this tag let counter = 1; $tempSection.find(tagName).each((_, elem) => { const elemHtml = $(elem).html() || ""; const elemText = $(elem).text().trim(); if (elemText) { const subId = `${baseId}-${tagName}-${counter}`; subsectionIds.push(subId); // Recursively check if this subsection needs further breakdown // Start from next tag in sequence (i+1) const subResult = breakDownSection( elemHtml, subId, `${baseTitle} [${tagName}]`, sourceFile, baseId, depth + 1 ); // If subsection was broken down further if (subResult.subsectionDocs.length > 0) { subsectionDocs.push(...subResult.subsectionDocs); // Also add the subsection's parent document if (subResult.parentDoc.subsections.length > 0) { subsectionDocs.push(new Document({ pageContent: [ `[Section ${subId}: ${baseTitle} [${tagName}]]`, "(Section content below - subsections marked inline)", "", "---", "", cheerio.load(subResult.parentDoc.content).text().trim() ].join("\n"), metadata: { source: sourceFile, sectionid: subId, sectiontitle: `${baseTitle} [${tagName}]`, type: "specification", parentsectionid: baseId, subsections: subResult.parentDoc.subsections, }, })); } } else { // Subsection is small enough, create leaf document subsectionDocs.push(new Document({ pageContent: elemText, metadata: { source: sourceFile, sectionid: subId, sectiontitle: `${baseTitle} [${tagName}]`, type: "specification", parentsectionid: baseId, subsections: [], }, })); } counter++; // Extract title from element if available let elemTitle = ""; if (tagName === "emu-table") { elemTitle = $(elem).find("caption").first().text().trim(); } else if (tagName === "emu-clause") { elemTitle = $(elem).find("h1").first().text().trim(); } // Replace with marker line in parent content const markerText = elemTitle ? `[Subsection available: title "${elemTitle}" at sectionid: \`${subId}\`]` : `[Subsection available at sectionid: \`${subId}\`]`; $(elem).replaceWith(`

${markerText}

`); } }); // Check if breakdown was effective remainingHtml = $tempSection.html() || ""; const remainingText = $tempSection.text().trim(); if (subsectionIds.length > 0) { tagUsed = tagName; // If remaining is small enough, stop here if (remainingText.length <= LARGE_DOC_THRESHOLD) { break; } } } return { parentDoc: { content: remainingHtml, tagUsed, subsections: subsectionIds }, subsectionDocs }; } ``` ### Document Creation with Subsections ```typescript async function ingestSpec(): Promise { const htmlFiles = await glob(path.join(SPEC_DIR, "*.html")); const documents: Document[] = []; for (const file of htmlFiles) { const content = fs.readFileSync(file, "utf-8"); const $ = cheerio.load(content); $("emu-clause").each((_i, elem) => { const id = $(elem).attr("id"); const title = $(elem).find("h1").first().text().trim(); const html = $(elem) .clone() .children("emu-clause") .remove() .end() .html() || ""; if (!id || !title || !html.trim()) { return; } // Apply recursive breakdown const result = breakDownSection(html, id, title, file, null, 0); // Create parent document if (result.parentDoc.subsections.length > 0) { const parentContent = [ `[Section ${id}: ${title}]`, "(Section content below - subsections marked inline)", "", "---", "", cheerio.load(result.parentDoc.content).text().trim() ].join("\n"); documents.push(new Document({ pageContent: parentContent, metadata: { source: file, sectionid: id, sectiontitle: title, type: "specification", parentsectionid: null, subsections: result.parentDoc.subsections, }, })); // Add all subsection documents (including recursively broken ones) documents.push(...result.subsectionDocs); } else { // No breakdown needed - add as-is documents.push(new Document({ pageContent: cheerio.load(html).text().trim(), metadata: { source: file, sectionid: id, sectiontitle: title, type: "specification", parentsectionid: null, subsections: [], }, })); } }); } return documents; } ``` ### Hierarchical Structure Recursive breakdown creates nested hierarchy: ``` sec-if-statement (parent) ├── sec-if-statement-emu-table-1 (parent) │ ├── sec-if-statement-emu-table-1-td-1 │ ├── sec-if-statement-emu-table-1-td-2 │ └── ... ├── sec-if-statement-emu-table-2 (leaf) ├── sec-if-statement-emu-grammar-1 (leaf) └── ... ``` **Breakdown type derivation:** From subsection ID pattern: - `sec-if-statement-emu-table-1` → broke down by `emu-table` - `sec-if-statement-emu-table-1-td-2` → table-1 broke down by `td` ### Querying Strategy **Get all chunks from a section (recursive):** ```typescript async function getAllChunks(sectionId: string): Promise { const allDocs: Document[] = []; const queue: string[] = [sectionId]; const visited = new Set(); while (queue.length > 0) { const currentId = queue.shift()!; if (visited.has(currentId)) continue; visited.add(currentId); const results = await table .query() .where(`sectionid = '${currentId}'`) .limit(100) .toArray(); for (const result of results) { allDocs.push(result); // If has subsections, add them to queue if (result.subsections && result.subsections.length > 0) { queue.push(...result.subsections); } } } return allDocs; } ``` ## Agent Usage ### Parent Discovery When the agent retrieves a parent chunk (has `subsections` array with items), it will see inline markers where content was extracted: ``` [Section sec-if-statement: If Statement] (Section content below - subsections marked inline) --- The if statement evaluates a condition... [Subsection available: title "Static Semantics: Early Errors" at sectionid: `sec-if-statement-emu-table-1`] The result of the evaluation determines... [Subsection available: title "IfStatement" at sectionid: `sec-if-statement-emu-grammar-1`] Further text continues... ``` ### Deep Breakdown Example When a subsection (like a large table) is also broken down: **Parent chunk:** ``` [Section sec-if-statement: If Statement] (Section content below - subsections marked inline) --- The if statement evaluates a condition... [Subsection available: title "Static Semantics: Early Errors" at sectionid: `sec-if-statement-emu-table-1`] [Subsection available: title "IfStatement" at sectionid: `sec-if-statement-emu-grammar-1`] The result of the evaluation determines... ``` **Subsection parent (table-1 broken down further by td):** ``` [Section sec-if-statement-emu-table-1: If Statement [emu-table]] (Section content below - subsections marked inline) --- Table header row... [Subsection available at sectionid: `sec-if-statement-emu-table-1-td-1`] [Subsection available at sectionid: `sec-if-statement-emu-table-1-td-2`] Table footer... ``` ``` **Leaf chunk (table cell):** ``` [Section sec-if-statement-emu-table-1-td-1: If Statement [emu-table] [td]] (Actual table cell content here) ``` ### Agent Strategy 1. Retrieve emu-clause by semantic search 2. Check if `subsections?.length > 0` to detect parent 3. Derive breakdown type from subsection IDs (e.g., `*-emu-table-*` means table breakdown) 4. Recursively check fetched subsections - they may also have subsections! 5. Continue until reaching leaf nodes (no subsections) 6. Combine all retrieved chunks for complete answer ## Changes to agent.ts ### Enhanced fetch_section_chunks Tool ```typescript const sectionRetrieverTool = new DynamicTool({ name: "fetch_section_chunks", description: "Retrieves all text chunks from a specific specification section by sectionid. " + "Supports recursive fetching - if a section has subsections, it will fetch all descendants. " + "Use this to get complete content when you see 'Subsection available' references in parent chunks.", func: async (sectionId) => { const allDocs: string[] = []; const queue: string[] = [sectionId]; const visited = new Set(); while (queue.length > 0) { const currentId = queue.shift()!; if (visited.has(currentId)) continue; visited.add(currentId); const results = await table .query() .where(`sectionid = '${currentId}'`) .limit(100) .toArray(); for (const result of results) { allDocs.push(result.text || ""); // Add subsections to queue for recursive fetching if (result.subsections && Array.isArray(result.subsections)) { queue.push(...result.subsections); } } } return allDocs.join("\n\n---\n\n"); } }); ``` ### New Check for Breakdown Tool (Optional) ```typescript const checkSubsectionsTool = new DynamicTool({ name: "check_subsections", description: "Checks if a section has subsections and returns their IDs. " + "Use when you need to selectively fetch specific subsection types (tables, grammar, prose).", func: async (sectionId) => { const result = await table .query() .where(`sectionid = '${sectionId}'`) .limit(1) .toArray(); if (result.length === 0) { return `No section found with id: ${sectionId}`; } const doc = result[0]; if (!doc.subsections || doc.subsections.length === 0) { return `Section ${sectionId} has no subsections.`; } return [ `Section ${sectionId} has ${doc.subsections.length} subsections:`, ...doc.subsections.map((id: string) => { const match = id.match(/-(table|grammar|prose)/); const type = match ? match[1] : 'subsection'; return ` - ${type}: ${id}`; }) ].join("\n"); } }); ``` ## Testing Checklist ### Basic Functionality - [ ] Run `bun run setup/ingest.ts` completes without errors - [ ] Verify parent chunks have `subsections` array with children - [ ] Verify leaf chunks have empty/null `subsections` - [ ] Verify subsection references are in parent chunk content - [ ] Verify subsection IDs show correct breakdown path (e.g., `section-emu-table-1`) ### Sequential Breakdown - [ ] Find a section with >5000 chars and tables, verify emu-table breakdown happens - [ ] Find a section with large grammar block, verify emu-grammar breakdown happens - [ ] Find a section where emu-table leaves large remainder, verify emu-grammar also extracted - [ ] Derive breakdown type from subsection IDs (first tag in ID path) ### Recursive Breakdown - [ ] Find a large table (section-emu-table-1 > 5000 chars), verify it gets further broken down - [ ] Check that nested breakdown uses next tags in sequence (emu-grammar, td, p) - [ ] Verify nested IDs: `sec-if-statement-emu-table-1-td-2` (table broken down by td) - [ ] Test max depth limit (3): deeply nested sections stop at depth 3 - [ ] Verify all subsections have `parentsectionid` pointing to their immediate parent ### Query Testing - [ ] Query for parent section, verify content includes subsection lines - [ ] Query for subsection by ID (e.g., `sec-if-statement-emu-table-1`) - [ ] Test `fetch_section_chunks` with parent ID returns all subsections - [ ] Verify agent can discover and fetch subsections automatically - [ ] Test agent query on large section to verify subsection discovery works ### Indexes - [ ] Verify scalar indexes are created for: sectionid, type - [ ] Test WHERE clause queries on indexed columns ## Limits & Edge Cases ### Sequential Breakdown Order Tags are tried in strict order: 1. `emu-table` - Best semantic unit for spec tables 2. `emu-grammar` - Grammar productions are natural boundaries 3. `td` - Table cells (only if tables couldn't reduce enough) 4. `p` - Paragraphs (last resort, breaks prose) **Rationale:** Structure-aware breakdown preserves semantic meaning better than arbitrary text splitting. ### Recursive Breakdown Logic - **Applied to every extracted subsection**: Each extracted chunk is also checked for size - **Sequential tags continue**: If `sec-table-1` is large, try next tags (emu-grammar, td, p) - **Depth tracking**: `depth` field shows nesting level (0 = emu-clause, max 3) - **ID nesting**: IDs reflect full path: `section-table-1-td-2` means table 1 was broken down by td ### Content Size Threshold - **Default**: 5000 characters (configurable via `LARGE_DOC_THRESHOLD`) - **Applied at every level**: Parent and all subsections checked against threshold - **Max depth safety**: Even if still large at depth 3, no further breakdown (prevents infinite recursion) ### Hierarchical Structure - **Nested IDs**: IDs show full breakdown path like `sec-if-statement-emu-table-1-td-2` - **Multi-level**: Subsections can be parents with their own children - **Parent tracking**: `parentsectionid` always points to immediate parent (could be subsection or emu-clause) ### Large Remainders at Max Depth If at depth 3 content is still > threshold: - Keep it as-is (chunk may exceed threshold) - Add warning in content: "(Content exceeds threshold - max depth reached)" - This is rare - depth 3 with td/p breakdowns should handle most cases ## Migration Strategy 1. **Clean slate**: Delete existing `storage/` directory 2. **Re-ingest**: Run `bun run setup/ingest.ts` 3. **Verify structure**: Check that large sections have hierarchical breakdowns 4. **Test breakdown types**: Look for examples of each breakdown type by ID pattern: ```typescript // Query to find emu-table breakdowns const tableBreakdowns = await table .query() .where(`sectionid LIKE '%-emu-table-%'`) .limit(10) .toArray(); console.log(tableBreakdowns.map(s => s.sectionid)); ``` 5. **Test recursive breakdown**: Find deeply nested examples: ```typescript // Query for nested breakdowns (table cells broken down) const nested = await table .query() .where(`sectionid LIKE '%-td-%'`) .limit(10) .toArray(); console.log(nested.map(s => ({id: s.sectionid, parent: s.parentsectionid}))); ``` 6. **Test agent**: Run queries on large sections like "sec-globaldeclarationinstantiation" 7. **Verify hierarchy**: Confirm parent-child chain is correct (e.g., `td` → `table` → `section`)