Architecture22 min readUpdated August 9, 2026

Memory Finds. Ontology Decides.

Semantic vault recall still returns near-misses on institutional questions. We open-sourced CQE — ClawQL’s entity definition format — and a legal Matter pack that turns escrow and non-compete into typed predicates. Here is the schema, the ingest path, the query, and the scores.

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:

  1. Field-name chaos. Escrow shows up as escrow, Escrow %, escrow_percent, CLAWQL_ESCROW_PCT. Keyword search and embeddings treat those as different worlds.
  2. 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.
  3. 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:

SetupWhat the agent gets
With ClawQL memoryThe same prose notes on disk, plus ClawQL tools, plus a seeded vault (typed fields written into ontology.db at ingest)
ClawQL tools, no vaultSame prose and tools, but nothing durable to query
Bare agentSame 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.db which tables and indexes to maintain beside memory.db,
  • tells memory_recall which schema + filters predicates 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:

  1. Extract fields (priority order below).
  2. Validate against the CQE schema (clawql ontology lint surfaces failures).
  3. Upsert into ontology.db next to memory.db under CLAWQL_OBSIDIAN_VAULT_PATH.
  4. Link discovered relationships back into the note as wikilinks when useful.

Extraction priority

PriorityMethodConfidenceNotes
1Machine-readable CLAWQL_* blocksEXTRACTEDWhat the mini-firm fixture uses; exact
2Conservative Markdown patternsINFERREDe.g. Escrow: 12% (24 months)
3LLM extraction (opt-in)INFERREDFrugal 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 fieldOntology fieldTypeNormalization
CLAWQL_MATTER_IDMatter.idMatterIDValidate /^MAT-\d{4}$/
CLAWQL_ESCROW_PCTMatter.escrowPctPercentageParse float, 0–100
CLAWQL_NONCOMPETE_MONTHSMatter.nonCompeteMonthsIntegerParse int
CLAWQL_DEAL_VALUE_USDMatter.dealValueUSDIntegerParse int
CLAWQL_CLIENT_IDMatter.clientClientRefValidate /^CLT-\d{4}$/
CLAWQL_PRACTICE_AREAMatter.practiceAreaPracticeAreaEnum match
CLAWQL_STATUSMatter.statusMatterStatusEnum match
CLAWQL_ESCROW_DURATION_MONTHSMatter.escrowDurationMonthsIntegerParse int
CLAWQL_NC_GEOGRAPHYMatter.nonCompeteGeographystringTrim

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):

SetupResult
ClawQL memory0 — near-misses counted as false positives
Bare agent0 — 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):

SetupResult
ClawQL memory + typed filtersPerfect scores across repeats — exact five-of-five, no extras
Bare agentNear zero — still stuck reading prose
ClawQL tools, no vaultZero — 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

  1. 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.
  2. Train models to reach for exact recall. Traces that record queryType: structured_predicate with schema: legal.Matter and filters are SFT / DPO / GRPO fuel. See When Rubrics Become Rewards.
  3. 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.
  4. 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/*.cqe without 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

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.