add_knowledge returned a success-shaped response with facts_created: 0 and no error, twice, on a 10-fact payload. The facts were not written. Batches of 1 and 3 through the same code path on the same run succeeded. Caught only by querying the database afterwards, which means any caller trusting the tool's own response would have believed the write landed.
Two behaviours, and the second makes the first harder to notice.
1. Silent drop on a larger batch. No exception, no partial-success warning, no field the caller can read to distinguish "nothing to write" from "write failed". facts_created: 0 is also the legitimate response to a genuinely empty payload, so the failure is indistinguishable from a no-op by design. Whatever the underlying cause (size, timeout, a swallowed exception), the contract problem is the same: the response says success and the store is unchanged.
2. Episode reuse keys on (source_document, group_id). On the first attempt this silently attached the payload to another document's existing episode rather than creating its own. So a caller can be handed a valid-looking episode id that belongs to a different source.
Encountered 2026-08-17 while ingesting two documents into substrate-dynamics-research; the rest of that run was clean (71 facts, 38 chunks, 50 entity links, 0 null embeddings, both gates strict PASS), so this is specific to the add_knowledge path rather than to extraction.
Suggested fixes, in priority order:
- Make a failed write unrepresentable as success. Either raise, or return an explicit status plus a
facts_attempted count alongside facts_created so attempted > created is detectable by the caller without a database query.
- If there is a batch-size or payload limit, enforce it loudly rather than dropping — reject with the limit named, or chunk internally and report per-chunk results.
- Return the episode's provenance in the response (
episode_id plus whether it was created or reused, and for which source_document), so silent reuse is visible at the call site.
This is the same class as other defects we have hit in this stack recently: a tool returning a complete-looking response while having stored nothing. A caller cannot defend against it except by re-querying the store, which defeats the point of the return value.
🤖 Generated with Claude Code
add_knowledgereturned a success-shaped response withfacts_created: 0and no error, twice, on a 10-fact payload. The facts were not written. Batches of 1 and 3 through the same code path on the same run succeeded. Caught only by querying the database afterwards, which means any caller trusting the tool's own response would have believed the write landed.Two behaviours, and the second makes the first harder to notice.
1. Silent drop on a larger batch. No exception, no partial-success warning, no field the caller can read to distinguish "nothing to write" from "write failed".
facts_created: 0is also the legitimate response to a genuinely empty payload, so the failure is indistinguishable from a no-op by design. Whatever the underlying cause (size, timeout, a swallowed exception), the contract problem is the same: the response says success and the store is unchanged.2. Episode reuse keys on
(source_document, group_id). On the first attempt this silently attached the payload to another document's existing episode rather than creating its own. So a caller can be handed a valid-looking episode id that belongs to a different source.Encountered 2026-08-17 while ingesting two documents into
substrate-dynamics-research; the rest of that run was clean (71 facts, 38 chunks, 50 entity links, 0 null embeddings, both gates strict PASS), so this is specific to theadd_knowledgepath rather than to extraction.Suggested fixes, in priority order:
facts_attemptedcount alongsidefacts_createdsoattempted > createdis detectable by the caller without a database query.episode_idplus whether it was created or reused, and for whichsource_document), so silent reuse is visible at the call site.This is the same class as other defects we have hit in this stack recently: a tool returning a complete-looking response while having stored nothing. A caller cannot defend against it except by re-querying the store, which defeats the point of the return value.
🤖 Generated with Claude Code