# Structured memory

Structured memory means storing what is known as typed records — a fact with a scope, an owner and a tag — instead of keeping a transcript and re-reading it. The difference is not storage efficiency. It is that a record can be queried, corrected, scoped, audited and deleted, and a transcript can only be searched and re-read.

- **Status:** Reference
- **Audience:** both, developer
- **In the app:** #/data
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/structured-memory/

## The same knowledge, two ways to keep it

| Operation | Transcript | Record |
|---|---|---|
| Find what applies to this contact | Search text and hope the phrasing matches | Select by scope and key |
| Correct something wrong | Append a correction and hope the later one wins | Edit the row; the old value stops applying at once |
| Forget one thing | Effectively impossible without deleting the conversation | Delete the row |
| Apply a rule narrowly | No mechanism | Set it at the narrower scope |
| Explain why the agent did that | Reconstruct from prose | Show the row that applied |
| Cost per turn | Grows with history | Bounded by the budget you set |

The second column is how most conversational systems remember, and it is why they drift. A correction buried at turn forty competes with the original statement at turn three, and the model resolves that competition however it resolves it. Making the correction a write to a record removes the competition instead of arbitrating it.

## Does Connect use structured memory?

**Used**, as the only memory design in the product. `connect_memory.py` holds four tiers, resolved narrowest-first: **workspace → channel → endpoint → contact**. An endpoint is one mailbox or one phone number. `tiers()` returns everything that applies to a piece of work, split by the tier that owns it — and an empty tier comes back empty rather than being dropped, so 'nothing is set at this level' is a visible answer instead of an absence you have to infer.

That hierarchy is the same one [autonomy](/docs/autonomy/) uses, deliberately. A person who has learned that the narrowest scope wins for what Connect may *do* already knows the rule for what Connect *knows*.

The design detail that shows the pattern best is blocking. Blocking a contact on a channel is **not a column**. It is a memory row against that contact, tagged `block:<channel>` in its tag list — because `directives()` reads only the tags, never the body. Keeping it as a tagged record rather than a boolean field is what makes a block hold across channels and across conversations that have not happened yet, and what makes it visible in the same place as everything else Connect knows about that person.

A person can read every tier and forget any of it, from the memory viewer mounted on the mailbox row, the channel screen and the contact panel. Memory that cannot be inspected is indistinguishable from a model's mood.

## What becomes possible

- **Scoped rules.** 'Never quote lead times to this one customer' is a contact-tier row that overrides a workspace-tier one, without a special case anywhere in the code.
- **Answerable questions about the agent.** What does Connect know about this mailbox? — a query, not an investigation.
- **Corrections that stick.** The next answer changes because the row changed. See [making a correction actually change behaviour](/research/memory-correction-that-sticks/).
- **A bounded prompt.** Memory contributes a fixed budget to each call — **700 characters** on the voice path — rather than a growing history.
- **Deletion that means something.** A deleted row stops applying immediately, which is a claim a transcript-based system cannot honestly make.
- **A grid over the whole of it.** Memory is one of the **13 sheets** on [Files and data](/docs/files-data/), alongside companies, people, leads, prospects, follow-ups, deals, conversations, calls, cases, onboarding, knowledge and files.

## The costs, stated plainly

**Something must decide what to keep** — A transcript keeps everything by default. Records require an extraction step, and anything not extracted is not remembered.
**Scope choice is a real decision** — A fact filed at the workspace tier that belonged at the contact tier will apply where it should not, quietly.
**Contradiction becomes explicit** — Two rows disagreeing at the same tier is a state you have to design for. In a transcript the same contradiction hides in prose.
**Migrations** — Records have a schema and a schema changes. `migrate.py` runs on every boot for exactly this reason.

> **Careful** Structured memory is not a licence to keep more. Every row is data about a person that somebody may later ask you to produce or delete, so the fact that it *can* be forgotten only helps if it also *is*.

## Questions

### Does structured memory mean the agent forgets the conversation?

No — the conversation is still a record in its own right, with its messages and its timeline. The distinction is which one is consulted when Connect needs to know something durable. Reading the thread tells you what was said; reading memory tells you what is true.

### Why keep a block as a tag rather than a field?

Because a field belongs to one table and one channel. A tagged row against the contact travels with the person, holds on channels that did not exist when it was set, and appears wherever memory appears. `directives()` reading only the tag list is what makes the behaviour reliable rather than dependent on wording.

### What stops memory growing until it costs too much?

The per-call budget, not the store. What is kept can grow; what is sent on any one call is capped, and tier resolution decides which rows earn the space.

## Related

- [Memory in Connect](https://connectbyjbrh.com/docs/memory/)
- [Structured business memory instead of a longer prompt](https://connectbyjbrh.com/research/structured-business-memory/)
- [Relationship memory](https://connectbyjbrh.com/docs/technology/relationship-memory/)
- [Context window](https://connectbyjbrh.com/docs/technology/context-window/)
- [Files and data in Connect](https://connectbyjbrh.com/docs/files-data/)

## What this page is based on

- Connect architecture source pack — docs-source/sources/GENERAL.md §3 and §6, the data model and memory
- Connect capability registry (docs-source/facts.py) — memory_tiers, memory_editing, block_directive
- `backend/app/connect_memory.py` — tiers() and directives(), per the source pack
