The first agent we really put into Rooftop's operation worked fine until someone asked "why".
It answered. Fast, confident, and occasionally with a fact from ten months ago that nobody had reviewed. When I asked where it came from, the best I could get was a chunk retrieved from the vector index. A piece of text with no owner, no date, no sense of who wrote it or why.
That isn't a model problem. It's a bookkeeping problem.
What was broken
We had the standard setup: documents go in, become embeddings, agent retrieves, agent answers. It works surprisingly well in a demo and starts cracking once the knowledge ages.
Three things broke often:
Facts with no provenance. The agent claimed a franchise unit had a certain payout window. It came from a 2024 PDF that had already been replaced. The index had no way to know — to it, text is text.
Silent contradiction. Two documents said different things about the same rule. The retriever returned whichever was semantically closest. Never the most recent, never the most trustworthy, just the most similar.
Nothing auditable. When a client asked why the agent recommended X, the honest answer was "because the model thought so". That does not survive a due diligence conversation.
I spent a while trying to fix this with prompting. Instructions to cite sources, instructions to say "I don't know", reranking, metadata filters. It improved at the margins. The hole stayed exactly where it was: the system had no place where knowledge was a record with an identity and a history. It had a bag of text.
The turn: treat knowledge as a ledger entry
The idea behind Credence comes from a fairly obvious place once you see it: accounting solved this six hundred years ago.
In a ledger you don't overwrite a number. You post a new entry that corrects the earlier one, and both stay. The balance is a projection of the history, not the history itself. If someone questions the balance, you walk the entries.
Agent knowledge should work the same way.
In Credence the unit isn't a chunk. It's a Claim: an assertion with a subject, key, value, status, and evidence. It's never deleted. The vocabulary is short on purpose: Claim, Evidence, Source. Unknown is not the same as absent — "I don't know" and "there is none" are different answers, and mixing them is like mixing a zero balance with a missing account.
Writing to the ledger is a recordClaim:
Each evidence item carries a stance: supports, contradicts, or cited. Omitting the field means unstated — and stance is never inferred. Status verified refuses evidence that does not support the claim; unstated still passes the guard. When two live claims disagree on the same subject and key, that becomes a finding, not a silent tie.
What actually changed
The part I didn't expect: the biggest change wasn't answer quality. It was the conversation about the answer.
Before, when the agent was wrong, the discussion turned into philosophy about models. Now you open the ledger and see which claim was used, which source it came from, with what stance, and who recorded it. Nine out of ten errors are input errors, not reasoning errors. A stale document nobody retired, a spreadsheet someone filled in wrong, a rule that changed over WhatsApp and never became a document.
Not glamorous. Exactly the kind of thing that makes a system survive in production.
Second change: contradiction became a signal rather than a bug. Two live claims on the same subjectEntityId + key are not merged. They become a finding. Someone decides. We caught operational rules that had diverged between franchises months earlier and nobody had noticed — not because the system is clever, but because it refuses to fake consensus.
Third: expiry and status became explicit. An FX quote is good for minutes. A contract rule holds until the amendment. The name of a unit's manager holds until someone changes jobs. Before, everything had the same half-life, which was infinite.
What isn't solved
I won't sell this as finished, because it isn't.
Extraction is still the bottleneck. Turning a 40-page PDF into well-formed claims is model work, and models get it wrong. The ledger makes the error visible and reversible, which is much better than invisible and permanent, but it doesn't stop it from getting in.
Granularity is a design decision, not a library feature. payout_window = 15 is a good claim. "This contract favors the franchisee" is not — it's compressed opinion, and it becomes garbage in the ledger. I learned that by writing about three hundred bad claims first.
Read cost. Projecting standing state from history is more expensive than reading a vector. We cache the projection and invalidate per subject, which is the same old trick with the same old problems.
And Credence doesn't replace vector search. It sits in front of it. Vectors are still great for finding the document; the ledger is for asserting the fact. Conflating the two was the original mistake.
Why publish this
The package is @credence/core 0.6.0 on the npm.pipedocs.app registry. The API still moves — we run it in production and every week reveals a wrong assumption.
I'm publishing because the interesting part isn't the code, it's the framing: agent memory looks more like accounting than like search. Someone saying that to my face in 2025 would have saved me three months.
If you're building something similar, the question worth asking early is this: a year from now, when your agent claims something, will you be able to explain where it came from? If the answer is no, the problem isn't the model.
