{"record":{"id":"16c6aad4bf509728","repo":"666ghj/MiroFish","slug":"zep-batch-batch-id-acknowledged-len-item-detail","errorCode":null,"errorMessage":"Zep batch {batch_id} acknowledged {len(item_details or [])} of {len(items)} items","messagePattern":"Zep batch (.+?) acknowledged (.+?) of (.+?) items","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":536,"sourceCode":"                        f\"Zep batch {batch_id} item submission failed\"\n                    ) from e\n\n            if len(item_details or []) != len(items):\n                recovered_items = self._reconcile_batch_item_count(\n                    batch_id,\n                    expected_item_count,\n                )\n                recovered_indexes = {\n                    getattr(item, \"sequence_index\", None)\n                    for item in recovered_items\n                }\n                if (\n                    len(recovered_items) == expected_item_count\n                    and recovered_indexes == set(range(expected_item_count))\n                ):\n                    item_details = recovered_items[i:expected_item_count]\n                else:\n                    raise RuntimeError(\n                        f\"Zep batch {batch_id} acknowledged {len(item_details or [])} \"\n                        f\"of {len(items)} items\"\n                    )\n            for item in item_details:\n                episode_uuid = getattr(item, \"episode_uuid\", None)\n                if episode_uuid:\n                    episode_uuids.append(episode_uuid)\n\n        try:\n            self.client.batch.process(batch_id=batch_id)\n        except Exception as error:\n            # A process response can be lost after the server accepted it.\n            # Reconcile with a safe GET instead of issuing a second POST.\n            summary = call_zep_read_with_retry(\n                lambda: self.client.batch.get(batch_id=batch_id),\n                operation_name=f\"reconcile batch {batch_id}\",\n            )\n            if getattr(summary, \"status\", None) in {None, \"draft\"}:","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L518-L554","documentation":"RuntimeError in submit_document_batch: after adding a group of items, the count of acknowledged item_details does not match len(items), so the code reconciles via _reconcile_batch_item_count and re-checks sequence_index completeness. If the reconciled list still is not exactly expected_item_count items with indexes range(expected_item_count), Zep acknowledged fewer items than were submitted and the mismatch is unrecoverable — proceeding would silently drop graph episodes.","triggerScenarios":"batch.add returns item_details for only part of the submitted group (server truncation or partial acceptance); the reconcile listing shows missing sequence indexes, meaning some chunks never landed; concurrent item additions to the same batch from another process skewing counts.","commonSituations":"Large batch groups near the 350-item limit under load; Zep Cloud eventually-consistent listing returning partial item sets; two workers resuming the same batch simultaneously.","solutions":["Retry via the resume flow once backend/Zep recovers — the persisted batch identity supports it.","Inspect the batch items in the Zep console and identify which sequence indexes are missing; if unrecoverable, delete the draft batch and rebuild.","Ensure only one worker processes a given operation at a time (per-project submission lock) so counts are not skewed by concurrency.","Lower batch_size to reduce per-request item counts if truncation recurs.","Report to Zep if add consistently acknowledges fewer items than sent with a 2xx."],"exampleFix":"# before\nelse:\n    raise RuntimeError(\n        f\"Zep batch {batch_id} acknowledged {len(item_details or [])} of {len(items)} items\"\n    )\n\n# after - name the missing indexes so operators can repair precisely\nelse:\n    acked = {getattr(it, \"sequence_index\", None) for it in (item_details or [])}\n    missing = sorted(set(range(expected_item_count)) - acked)\n    raise RuntimeError(\n        f\"Zep batch {batch_id} acknowledged {len(item_details or [])} of {len(items)} items \"\n        f\"(missing sequence indexes: {missing[:20]}{'...' if len(missing) > 20 else ''})\"\n    )","handlingStrategy":"validation","validationCode":"expected = len(items)\nrecovered = builder._reconcile_batch_item_count(batch_id, expected)\nindexes = {getattr(it, 'sequence_index', None) for it in recovered}\nif not (len(recovered) == expected and indexes == set(range(expected))):\n    raise RuntimeError(f'Batch {batch_id} incomplete; missing {sorted(set(range(expected)) - indexes)}')","typeGuard":null,"tryCatchPattern":"try:\n    submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)\nexcept RuntimeError as e:\n    if 'acknowledged' in str(e) and 'of' in str(e):\n        # item set is provably incomplete; delete the draft batch and rebuild rather than resume\n        logger.error('Incomplete batch acknowledgment: %s', str(e))\n        raise\n    raise","preventionTips":["Enforce single-writer per batch (per-project lock) so counts are not skewed by concurrency.","Check acknowledged-vs-submitted counts after every add group, as this code does, and reconcile immediately.","Keep batch_size well under 350 when Zep is under load to reduce truncation risk.","If a batch is incomplete, delete the draft and restart instead of layering a second partial submission."],"tags":["backend","python","zep","batch","partial-failure","data-integrity"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}