Harvey’s published failure mode for institutional-knowledge agents is simple: the agent finds some of the matters, stops confidently, and files an incomplete answer. We rebuilt that failure in our own harness. Adding vault memory alone did not fix it. Typed predicates did — and those predicates are not a prompt trick. They come from CQE: ClawQL’s open entity-definition format, plus a legal domain pack that tells ingest how to normalize messy matter notes into ontology.db.
This pairs with Enterprise Ontology, the agent memory stack, the institutional knowledge tax, and When Rubrics Become Rewards.
The false win
The most important failure we recorded was not “the agent forgot to use memory.”
It was the opposite. The agent with ClawQL memory called memory_recall successfully. The tool returned. The agent wrote a results file. The grader scored zero.
Why? Semantic recall is approximate. Approximate recall produces near-misses — notes that sound like matches. Our grader, matching what a firm actually needs, hard-zeros on any false positive. Eighteen near-misses is not “almost right.” It is a complete miss of the only metric that matters: the exact set, nothing else.
Same shape as Harvey’s story. Not “couldn’t search.” Confident incompleteness.
If you only remember one sentence from this post:
Memory without typed predicates is still guessing.
What the question really is
“Institutional knowledge” sounds like a retrieval problem. In practice it is an enumeration under constraints problem:
- Find every matter where escrow is at least some threshold.
- Reconstruct a client’s governing-law or dispute-resolution preference across matters.
- Answer five related questions in one session without re-reading the whole corpus each time.
Three things break naive agent loops — and they map 1:1 to what the legal CQE pack has to fix:
- Field-name chaos. Escrow shows up as
escrow,Escrow %,escrow_percent,CLAWQL_ESCROW_PCT. Keyword search and embeddings treat those as different worlds. - Near-miss semantics. “High escrow” and “escrow ≥ 10%” feel similar to a model. A 9% matter is a false positive under a ≥10% rule. Similarity cannot enforce that; a typed predicate can.
- Scale. Exhaustive reading burns the turn budget. Predicate evaluation over an index is O(1) in corpus size; reading every note is not.
Vault memory solves storage and recall. It does not, by itself, solve set closure — every match, no extras. For that you need a schema the ingest path and the recall path both speak.
What we thought would be enough
We already had evidence that ClawQL memory helps in ordinary ways: continuing across turns, staying inside a token budget, chaining multi-provider work. So the natural bet was: seed a small synthetic firm vault, give the agent memory_recall, watch it win.
That bet failed in instructive ways.
On smaller fixtures, a stubborn bare agent could still finish by reading files. On larger ones, the memory-enabled agent sometimes recalled everything and still failed when it came time to write a clean answer. And in one early run that looked like a clean win, we later found a confound: the memory-enabled agent never even saw the same workspace files as the baseline. We retired that claim.
If you are going to argue that ontology unlocked the win, the comparison has to be honest first.
A fair comparison
The redesign that stuck:
| Setup | What the agent gets |
|---|---|
| With ClawQL memory | The same prose notes on disk, plus ClawQL tools, plus a seeded vault (typed fields written into ontology.db at ingest) |
| ClawQL tools, no vault | Same prose and tools, but nothing durable to query |
| Bare agent | Same prose only — no ClawQL |
Scoring rule: count how many of the five ground-truth matters the agent found, and any false positive zeros the whole score. Partial credit is fine. Inventing an extra matter is not.
No hidden cheat sheets. Same notes. Different decision machinery — and that machinery starts with CQE.
CQE: the thing we actually open-sourced
CQE (ClawQL Entity) is the entity-definition format for clawql-ontology. It is a proprietary standard we are open-sourcing: YAML documents with apiVersion: clawql.dev/ontology/v1alpha1, kind: Entity, typed properties, SQL source bindings, relationships, and read actions. Domain packs live under packages/clawql-ontology/packs/ — legal is packs/legal/entities/*.cqe.
CQE is not “another RAG config.” It is the contract that:
- tells ingest which fields exist and how they type-check,
- tells
ontology.dbwhich tables and indexes to maintain besidememory.db, - tells
memory_recallwhichschema+filterspredicates are legal.
Without CQE, “ontology” is a blog word. With CQE, escrow is escrow_pct: number on Matter, backed by a row in SQLite, filterable with { "gte": 10 }.
The Matter entity (shipped .cqe)
This is the file that made the enumeration win possible — shortened only for the unrelated status enum noise; properties and relationships are the real pack:
# packages/clawql-ontology/packs/legal/entities/Matter.cqe
apiVersion: clawql.dev/ontology/v1alpha1
kind: Entity
metadata:
name: Matter
labels:
vertical: legal
spec:
description: >
Legal matter / deal file. B-7.1 filter fields (escrowPct, nonCompeteMonths)
power structured memory_recall against ontology.db.
properties:
matter_id:
type: string
required: true
indexed: true
description: MatterID /^MAT-\d{4}$/
title:
type: string
required: true
status:
type: enum
values: [Active, Closed, Pending, OnHold]
required: true
practice_area:
type: enum
values: [MA, IP, Litigation, RealEstate, Employment, Corporate, Tax, Other]
matter_type:
type: enum
values:
[
Acquisition,
Merger,
Divestiture,
JointVenture,
AssetSale,
StockSale,
IPLicense,
Dispute,
Advisory,
Other,
]
deal_value_usd:
type: integer
description: Deal value in whole USD
escrow_pct:
type: number
description: Escrow holdback percentage 0–100 (B-7.1)
escrow_duration_months:
type: integer
non_compete_months:
type: integer
description: Non-compete duration in months (B-7.1)
non_compete_geography:
type: string
client_id:
type: string
description: ClientID /^CLT-\d{4}$/
vault_note_path:
type: string
sources:
- type: sql
connection: ${VAULT:ontology_db}
table: matters
id_column: id
relationships:
- entity: Client
type: many_to_one
via: client_id
- entity: Document
type: one_to_many
via: matter_id
actions:
- name: search_matters
kind: read
- name: get_matter
kind: read
API callers see camelCase (escrowPct); CQE and SQL use snake_case (escrow_pct). Same field.
Logical view (what the pack is saying)
The legal domain spec also carries a TypeScript-shaped view of the same entity — useful when you want to read the deal-economics surface without YAML. This is the Matter model the filters target:
// Logical view of packs/legal/entities/Matter.cqe
entity Matter {
id: MatterID // MAT-XXXX, required, unique
title: string
status: MatterStatus // Active | Closed | Pending | OnHold
practiceArea: PracticeArea // MA | IP | Litigation | …
matterType: MatterType // Acquisition | Merger | …
jurisdiction: string?
// Deal economics — the B-7.1 filter fields
dealValueUSD: Integer?
escrowPct: Percentage? // 0.0–100.0
escrowDurationMonths: Integer?
nonCompeteMonths: Integer?
nonCompeteGeography: string?
client: ClientRef // → Client, required
counterparty: string?
supervisionPartner: AttorneyRef?
billingPartner: AttorneyRef?
leadAssociate: AttorneyRef?
openedDate: ISODate?
closedDate: ISODate?
expectedCloseDate: ISODate?
billingType: BillingType?
totalBilledUSD: Integer?
totalHours: Float?
vaultNoteTitle: string
vaultNotePath: string
lastIngestedAt: ISODateTime
ingestVersion: string
relationships {
relatedMatters: MatterRef[]
workProduct: DocumentRef[]
priorMatters: MatterRef[]
}
}
Client is the join key for preference reconstruction — also a real .cqe in the pack:
# packages/clawql-ontology/packs/legal/entities/Client.cqe
apiVersion: clawql.dev/ontology/v1alpha1
kind: Entity
metadata:
name: Client
labels:
vertical: legal
spec:
description: Client / legal entity for matters (legal-domain-v0.1)
properties:
client_id:
type: string
required: true
indexed: true
description: ClientID /^CLT-\d{4}$/
name:
type: string
required: true
short_name:
type: string
industry:
type: string
jurisdiction:
type: string
tier:
type: enum
values: [Platinum, Gold, Silver, Standard]
vault_note_path:
type: string
sources:
- type: sql
connection: ${VAULT:ontology_db}
table: clients
id_column: id
relationships:
- entity: Matter
type: one_to_many
via: client_id
actions:
- name: search_clients
kind: read
- name: get_client
kind: read
Attorney and Document ship the same way (Attorney.cqe, Document.cqe / related pack entities). Small model. Enough to enumerate deals and reconstruct client history. Not a second CMS.
Ingest: how vault prose becomes typed rows
When memory_ingest writes a legal note, an ontology pass runs beside the Markdown write:
- Extract fields (priority order below).
- Validate against the CQE schema (
clawql ontology lintsurfaces failures). - Upsert into
ontology.dbnext tomemory.dbunderCLAWQL_OBSIDIAN_VAULT_PATH. - Link discovered relationships back into the note as wikilinks when useful.
Extraction priority
| Priority | Method | Confidence | Notes |
|---|---|---|---|
| 1 | Machine-readable CLAWQL_* blocks | EXTRACTED | What the mini-firm fixture uses; exact |
| 2 | Conservative Markdown patterns | INFERRED | e.g. Escrow: 12% (24 months) |
| 3 | LLM extraction (opt-in) | INFERRED | Frugal tier; off for the mechanism benchmark |
Machine-readable wins because it is boring:
CLAWQL_MATTER_ID=MAT-2401
CLAWQL_ESCROW_PCT=12
CLAWQL_NONCOMPETE_MONTHS=24
CLAWQL_DEAL_VALUE_USD=45000000
CLAWQL_CLIENT_ID=CLT-0104
Field map (CLAWQL_* → Matter)
| Raw field | Ontology field | Type | Normalization |
|---|---|---|---|
CLAWQL_MATTER_ID | Matter.id | MatterID | Validate /^MAT-\d{4}$/ |
CLAWQL_ESCROW_PCT | Matter.escrowPct | Percentage | Parse float, 0–100 |
CLAWQL_NONCOMPETE_MONTHS | Matter.nonCompeteMonths | Integer | Parse int |
CLAWQL_DEAL_VALUE_USD | Matter.dealValueUSD | Integer | Parse int |
CLAWQL_CLIENT_ID | Matter.client | ClientRef | Validate /^CLT-\d{4}$/ |
CLAWQL_PRACTICE_AREA | Matter.practiceArea | PracticeArea | Enum match |
CLAWQL_STATUS | Matter.status | MatterStatus | Enum match |
CLAWQL_ESCROW_DURATION_MONTHS | Matter.escrowDurationMonths | Integer | Parse int |
CLAWQL_NC_GEOGRAPHY | Matter.nonCompeteGeography | string | Trim |
Pattern fallbacks (only when blocks are missing) are intentionally conservative — e.g. /escrow[:\s]+(\d+(?:\.\d+)?)\s*%/i for escrowPct. Conflicting extractions get AMBIGUOUS. Confidence tags travel with query hits so agents can demand EXTRACTED only.
Lint is the operator loop: a matter missing escrow_pct is a data quality warning before it becomes a fake “product can’t enumerate” story.
$ clawql ontology lint --domain legal --vault ~/.ClawQL
✓ MAT-2401 all required fields present (EXTRACTED)
⚠ MAT-2433 escrow_pct missing
⚠ MAT-2441 non_compete_months AMBIGUOUS (18 vs 24)
The index beside the vault
CQE’s sources block binds Matter to SQL. Conceptually (and in the vault):
CREATE TABLE matters (
id TEXT PRIMARY KEY, -- MAT-XXXX
title TEXT,
status TEXT,
practice_area TEXT,
matter_type TEXT,
deal_value_usd INTEGER,
escrow_pct REAL,
escrow_duration_months INTEGER,
non_compete_months INTEGER,
non_compete_geography TEXT,
client_id TEXT,
vault_note_path TEXT NOT NULL,
last_ingested_at TEXT NOT NULL,
ingest_version TEXT NOT NULL
);
CREATE INDEX idx_matters_escrow ON matters(escrow_pct);
CREATE INDEX idx_matters_nc_months ON matters(non_compete_months);
CREATE INDEX idx_matters_client ON matters(client_id);
Prose stays in the vault. Predicates hit ontology.db. That split is the whole product idea.
Recall: the query that closed the set
memory_recall grew structured parameters — schema, filters, optional confidenceMinimum — in the structured filter extension. Once Matter is in CQE and the index is populated, enumeration stops being a vibe:
{
"query": "matters with escrow and non-compete clauses",
"schema": "legal.Matter",
"filters": {
"escrowPct": { "gte": 10 },
"nonCompeteMonths": { "gt": 18 }
},
"confidenceMinimum": "EXTRACTED",
"limit": 20
}
Supported predicates include eq, ne, gt, gte, lt, lte, in, contains, between. The response shape for the successful runs looked like this:
{
"hits": [
{ "id": "MAT-2388", "escrowPct": 15, "nonCompeteMonths": 24, "confidence": "EXTRACTED" },
{ "id": "MAT-2401", "escrowPct": 12, "nonCompeteMonths": 24, "confidence": "EXTRACTED" },
{ "id": "MAT-2415", "escrowPct": 18, "nonCompeteMonths": 36, "confidence": "EXTRACTED" },
{ "id": "MAT-2450", "escrowPct": 10, "nonCompeteMonths": 20, "confidence": "EXTRACTED" },
{ "id": "MAT-2462", "escrowPct": 22, "nonCompeteMonths": 24, "confidence": "EXTRACTED" }
],
"queryType": "structured_predicate",
"indexUsed": "ontology",
"scannedEntities": 12,
"filteredEntities": 5
}
A matter at 9% escrow never appears. gte: 10 is arithmetic on escrow_pct, not cosine similarity on “high escrow.” Decoy partial lists in the prose stop mattering: the agent is not reconstructing the set by reading every note.
Turn shape for the successful runs: one structured recall, one write. Same cost class at 12 matters or 250 — Harvey’s scale failure is an O(n) read loop; CQE + ontology.db makes set closure an index filter.
What changed when we ran it
Same task. Same notes. Same model family.
Without ontology (keyword / semantic recall only):
| Setup | Result |
|---|---|
| ClawQL memory | 0 — near-misses counted as false positives |
| Bare agent | 0 — could not finish a honest prose scan under the turn/token cap |
The memory agent did the “right” product thing — it called recall — and still lost. Approximate hits plus a hard-zero false-positive rule is a trap. There was no CQE-backed predicate path yet, so “use memory” still meant “rank prose.”
With ontology (CQE legal pack + structured filters on the same fair design):
| Setup | Result |
|---|---|
| ClawQL memory + typed filters | Perfect scores across repeats — exact five-of-five, no extras |
| Bare agent | Near zero — still stuck reading prose |
| ClawQL tools, no vault | Zero — tools without typed storage still have nothing exact to ask |
Memory was present in both eras. The difference was whether recall could express exact membership against fields defined in Matter.cqe.
We then stacked harder versions of the same idea:
Invent the filter yourself. Same corpus, but the prompt does not hand the agent the JSON shape. It still has to choose structured recall against legal.Matter. It did — which answers “did we just prompt-engineer the filter?” No. CQE made the right move available; the model still had to take it.
Reconstruct a client preference. Not “list every matching ID,” but “across this client’s matters, what do they usually want on the term sheet?” That is a join along Matter → Client (client_id / Client.cqe). Memory alone is not enough; bare search under a cap is worse.
Five related questions in one session. Question 1 pays for ingest and index population against the CQE pack. Questions 2–5 hit the index. With a typed vault, completeness stays high and cost amortizes. Without it, the baseline stays near zero.
We keep these results as reproducible mechanism tests on a synthetic mini-firm that preserves the failure mode. They are not Harvey’s public Legal Agent Benchmark scoreboard. That is a larger corpus and a different rubric. Do not blur the two. The path to that scoreboard is the same CQE pack, fair two-arm design, Opus-on-Opus when we publish numbers.
Why “just add memory” is the wrong abstraction
Memory answers: what have we stored, and what looks relevant?
Ontology answers: which entities satisfy this predicate, under this CQE schema, with this field semantics?
Agents fail institutional tasks when they optimize for relevance. Firms care about closure: every matching matter, no extras, provenance intact.
That is why “tools but no vault” stayed at zero — there was nothing typed to query. And why “memory with keyword recall” could call the right tool and still score zero — the tool returned a similarity neighborhood, not a set defined by Matter.cqe.
Recall is the surface. CQE is the decision contract.
What this unlocks
- Take the same failure mode to a larger firm corpus. Harvey’s Legal Agent Benchmark is the public scoreboard. Same Matter predicates, fair two-arm design, criterion and all-pass rates reported separately.
- Train models to reach for exact recall. Traces that record
queryType: structured_predicatewithschema: legal.Matterand filters are SFT / DPO / GRPO fuel. See When Rubrics Become Rewards. - See regressions in telemetry. If a future change silently falls back to keyword search, the retrieval node should say so. Lint warnings on missing CQE fields catch data bugs before they look like product bugs.
- Reuse the format. Legal is the first vertical pack. CQE is the standard — lending, government outcome records, and other domains get their own
packs/<vertical>/entities/*.cqewithout inventing a new ontology system each time.
Close
The agent that used memory and still scored zero is the story.
We did not need a larger context window. We did not need a louder embedding model. We needed an open entity format, a legal Matter pack that named escrow_pct and non_compete_months, an ingest path that filled ontology.db, and a recall path that filtered instead of guessed.
That format is CQE. The pack is packs/legal/entities/. The proof is perfect five-of-five on a hard-zero false-positive grader — after semantic memory scored zero on the same notes.
Memory finds.
Ontology decides.
CQE is how we write the decision down.
Further reading
- Legal CQE pack —
Matter.cqe·Client.cqe· legal-domain-v0.1 memory_recallstructured filter- Enterprise Ontology · Agent Memory Stack · Institutional Knowledge Tax
- When Rubrics Become Rewards · What Convergence Week Actually Proved
- Harvey’s Legal Agent Benchmark — harvey.ai blog
- OpenBench ledger (B-7.1 WIN + Convergence Week cells) — openbench-results-ledger.md
- Internal run notes — openbench-b7-calderwood.md
