Eighty characters of signal, and no memories at all
A bounded regex meant the extractor could read a short sentence and nothing longer. 521 ingestion tests passed against a detector that produced nothing from an ordinary paragraph.
The signal detector is the first thing that happens to a captured document. Before a model sees anything, eight regular expressions scan the text for clauses that look like a decision, a task, a reminder, a commitment. Each match becomes a candidate. Candidates become memories. If the detector finds nothing, ingestion still finishes cleanly: the document is stored, marked extracted, and reported as a success, because a document that says nothing worth remembering is a legitimate outcome and looks exactly the same from outside.
For a long time every one of those eight rules captured its clause the same way.
// The clause a rule captures, terminated by sentence punctuation or the
// end of the line. The 80 was picked as "a clause is not longer than this".
const DECISION = /\b(?:we|i)\s+(?:decided|agreed|chose)\s+(.{3,80}?)(?=[.!?]|$)/i;Read that as at least three characters and at most eighty, lazily. The mistake is in what the upper bound does. In a lazy bounded quantifier the bound is not a truncation of what you capture, it is how far the engine will look for the terminator. Past eighty characters with no period, question mark, exclamation mark or line end in sight, the rule does not fall back to a shorter match. It does not match at all.
"We decided to use PostgreSQL." -> detected
"We decided to use PostgreSQL with pgvector for the memory
store, after Sam Patel argued Pinecone would lock us in." -> nothingMeasured precisely afterwards: a 78 character clause produced a signal and an 82 character one produced none. The second sentence is what a person actually writes. No signals means no candidates, which means no memories, and all eight rules carried the same bound, so decisions, tasks, reminders, commitments and the rest failed together on any realistic sentence. The product's core function returned nothing, with no error anywhere.
Why the tests were no help
There were 521 tests over ingestion at the time, and every one of them passed. They passed because every fixture in them was a short sentence. Someone writing a test for a decision rule writes the shortest string that exercises the rule, which is the whole point of a unit test and also the reason this survived. The unit was correct on the inputs it was given. The inputs it was given were not the ones it gets.
This is the characteristic failure of a suite written from the same head as the code. Fixtures inherit the author's model of the input, including the part of that model that is wrong. A green suite is evidence that the code does what its author believed, not that the belief was right. Adding a 522nd test of the same shape would have added nothing.
What found it was running one capture end to end against a real database and a real worker, and then going to look at what came out. One document. Zero candidates. Zero memories. No errors. There was no red light for the pipeline to show, because from the pipeline's point of view nothing had gone wrong.
The fix is still a bound
The bound went to 400, which covers any ordinary sentence with room to spare. It did not become an unbounded quantifier, and the reason is the other half of the same property. A bounded lazy quantifier is what keeps a pathological input linear: a very long line with no terminator in it must not make the engine scan to the end of the document from every starting position in the document. Four hundred is a limit on how much wasted work one bad line can cause.
// 400, not `*`. The bound is a search distance, not a truncation, so it has
// to be long enough for a written sentence and short enough that a line with
// no terminator cannot make the scan quadratic.
const DECISION = /\b(?:we|i)\s+(?:decided|agreed|chose)\s+(.{3,400}?)(?=[.!?]|$)/i;That was verified rather than assumed: 200,000 characters of terminator-free text scans in 49ms, which is linear and comfortably inside any budget a document worker has.
What the tests look like now
Three tests pin the behaviour, one of which is a length sweep that walks a clause across the old cliff from 60 characters to 200 and asserts a signal at every step. Two of the three were checked against the old bound and fail there. A test that passes both before and after the fix is not pinning the fix, it is decoration, and the only way to know which kind you wrote is to put the bug back for a minute.
The end to end run is now the acceptance criterion rather than the suite. After the change, one capture went ingest to extract to consolidate to embed and produced 2 candidates, 2 memories, 2 vectors and 2 entities, and a search for what did we decide about deployment returned the decision at score 0.63 from both the structured and the semantic strategy, not degraded. That paragraph is the thing worth writing down. A test count is not a claim about the product; it is a claim about the tests.