A stored fact can be wrong in a quiet way. The answer still reads clean. A preference from an old exchange gets reused, the message goes out with confidence, and later nobody can tell why that detail was allowed back into the result.
That is the failure I built around. When a system returns remembered material, the caller needs the text plus the reason it passed the reuse check. A log line found after the action is weak evidence. The object that leaves the memory service has to carry the admission record with it.
1. Keep the outside surface small
This is the pattern I used in Holographic, Law-Bound Memory (HLM), a stand-alone memory brain outside application code. The README describes public Application Programming Interface (API) routes under /api/brain/*, with internal /api/v1/* services behind that layer.
The outside shape is intentionally thin: register an agent, write a fact, build a capsule. The Python Software Development Kit (SDK) in sdks/python/hlm_sdk/client.py shows the boundary without exposing table names or policy code:
import httpx
class HLMClient:
def __init__(self, base_url: str, token: str | None = None):
self.base_url = base_url.rstrip("/")
self._client = httpx.AsyncClient(headers={"Authorization": f"Bearer {token}"} if token else None)
async def register_agent(self, name: str):
r = await self._client.post(f"{self.base_url}/api/brain/agents/register", json={"name": name})
r.raise_for_status
return r.json
async def write_fact(self, text: str, tags: list[str] | None = None, selectors: list[str] | None = None):
r = await self._client.post(f"{self.base_url}/api/brain/memory/facts",
json={"text": text, "tags": tags or [], "selectors": selectors or []})
r.raise_for_status
return r.json
async def build_capsule(self, query: str, budget_tokens: int = 2048):
r = await self._client.post(f"{self.base_url}/api/brain/context/capsule",
json={"query": query, "budget_tokens": budget_tokens})
r.raise_for_status
return r.json
The TypeScript client in sdks/node/src/index.ts exposes the same calls as registerAgent, writeFact, and buildCapsule. That costs me a compatibility surface at the gateway. I accept the cost because admission policy in every consumer becomes drift. One caller skips a selector, another copies an old threshold, a third treats a nearby match as enough. Centralizing the decision gives the service a place to say yes or rebuild before the application acts.
2. Write facts with handles the service can check
A plain text memory is easy to save. It gives retrieval very little to inspect later. HLM writes each fact with tags, selectors, and an optional tenant field so the service has decision axes before a query shows up.
The write model in services/memory/app/main.py is small:
from typing import List
from pydantic import BaseModel, Field
class FactIn(BaseModel):
text: str
tags: List[str] = Field(default_factory=list)
selectors: List[str] = Field(default_factory=list)
tenant_id: str | None = None
That class feeds create_fact. The row itself lands in brain_facts. Two more writes follow against the same fact id, one for facets and one for predicates. The response returns the new id with both sets attached.
generate_facets has two paths in the current scaffold. A known selector value produces a specific facet row. Anything empty or unmatched falls back to a general facet, built from the first 256 characters of the text with the token count capped at 64. Those are constants in the code rather than performance claims. What they show is the shape: retrieval sees more than a blob of prose.
generate_predicates turns selector strings into one predicate joined with AND, swapping the first colon for =. That is rough. It is also enough, because the fact now leaves the write path carrying handles a machine can check. The tradeoff lands on the writer. A caller that sends empty selectors can still store text, but later selection has fewer axes to test.
3. Decide admission before capsule assembly
The reuse service is Conformal-Causal Reuse (CCR). Its request model in services/ccr/app/main.py carries the cache key, artifact type, selectors, and optional numeric controls:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="HLM CCR", version="0.1.0")
class CCRRequest(BaseModel):
tenant_id: str | None = None
cache_key: str
artifact_type: str = "resume_kit"
selectors: list[str] = Field(default_factory=list)
similarity: float | None = None
tau: float | None = None
The rule in reuse_or_rebuild is direct: a hit requires similarity > tau and the required selector kinds stakeholder, time, and channel to be present. Missing one of those kinds makes the service return rebuild. When the request omits values, the code uses 0.9 for similarity and 0.8 for tau. Those numbers are defaults in the function. They are not measured latency, quality, or production calibration.
The response includes decision, tau, similarity, and causal_ok. All four travel with the answer. Accepted material can name the rule that admitted it, and a rejection arrives as a rebuild decision instead of a silent empty match.
Calibration stays beside the same service. CalibIn accepts selector, similarity, and span_error; update_calibration computes a rounded tau and clamps it between 0.5 and 0.95. I kept that logic near the decision endpoint because threshold repair separated from the admission rule becomes another place for drift. The cost is coupling. CCR owns both the current decision and the local adjustment path.
4. Build the capsule with provenance attached
The orchestrator joins the pieces. In services/orchestrator/app/main.py, /api/v1/capsule derives selectors from the query and posts them to CCR. What comes back shapes the capsule: content, confidence values, reasoning metadata, and a proof value.
The intended object is signed context rather than an anonymous bag of nearest neighbors. The current branch is still a scaffold. Episode writes return receipt: "merkle:demo", and the orchestrator repeats that same demo value in its local capsule response. The slot is real and the hardening is unfinished, so the caveat stays in the design.
The four-step path is the engineering pattern:
flowchart TD
factWrite[Fact write] --> governedReuse[Reuse decision]
governedReuse --> capsuleBuild[Capsule build]
capsuleBuild --> proofReceipt[Proof receipt]
services/gateway/app/main.py contains merkle_root(items). It hashes the item strings and folds pairs until one hash is left, duplicating the last leaf when the count is odd. The result gets a merkle: prefix. The gateway is the right home for it, because the external object is formed there. Downstream code should receive a single object holding the selected material, the CCR decision fields, and a provenance value it can store or compare later.
The architecture document names this Proof-of-Context (PoC): Merkle roots over snapshot, version, tau, model, and ids. The label matters less than the placement. If applications learn to consume loose context first, provenance turns into a retrofit. Retrofitted evidence is usually optional. Optional evidence disappears under deadline pressure.
5. Preserve the same object across streamed updates
Memory work does not always end at the first capsule. services/orchestrator/app/main.py has a local loop that yields Server-Sent Events (SSE) through a StreamingResponse; each packet includes a generated packet_id, summary fields, next actions, and a timestamp before the loop pauses. The gateway forwards this through /api/brain/context/stream. The README and architecture notes describe the larger outbox path with leases, backoff, a dead-letter queue (DLQ), and a resume stream over SSE or WebSockets (WS). The current code handles the visible stream contract; the documented shape says lineage has to move with later packets as well. That adds overhead compared with returning an array from a nearest-neighbor endpoint, but a resumed update without the original admission data is just another loose event.
6. Own the memory lifecycle
This design buys safer reuse by moving work into the memory service. Writers must send useful selectors. The gateway becomes stricter. The service has to keep admission metadata and provenance beside the content from write, through CCR, into capsule creation and streaming. I prefer that pressure inside HLM over spreading half-copied rules through applications, because systems that remember should expose the conditions under which memory became usable.
๐ง Listen to the audiobook โ Spotify ยท Google Play ยท All platforms
๐ฌ Watch the visual overviews on YouTube
๐ Read the full 13-part series
Top comments (0)