Memory Layer
Context is what's active in an agent session. Memory is what persists. Jet's memory layer has two shapes: an append-only log of raw events, and a curated store of summarized entries. A daily compactor folds old events into summaries via the LLM so memory stays rich and lean indefinitely.
The two tables
memory_events — the raw log
Every meaningful moment gets an event: a call summary, a strategic decision, a campaign change, a sentiment shift. Automations and webhooks append to this table as things happen. Events are immutable and scoped.
- scope —
institutional|entity|project|team - scope_ref — entity_id / project_id / user_id depending on scope
- kind —
call_summary,decision,campaign_change, ... - body — full raw content (Markdown)
- source + source_ref — where it came from (inbox_message_id, automation_run_id, ...)
- occurred_at — when it happened
- compressed_at — set by the compactor when the event is folded into a summary
memory_entries — curated summaries
This is what the context loader can reference when loading memory into a session. Each entry is a discrete, searchable, scored memory item.
- scope + scope_ref — same shape as events
- title — one-line identifier
- summary — Markdown content
- tags — JSON array, used for filtering
- importance — 1 to 5; retention priority when compressing
- source —
chat|call|email|manual|compactor
The daily compactor
At 06:17 UTC every day, services/memoryCompactor.ts fires across
every active tenant. For each (scope, scope_ref) bucket with three or more
uncompressed events older than 90 days, the compactor:
- Pulls all uncompressed events in that bucket
- Sends them to the LLM with a compression prompt: preserve every strategic decision, relationship dynamic, and recurring pattern; drop the meeting-by-meeting prose
- Inserts the summary as a new
memory_entriesrow withsource='compactor'andimportance=4 - Marks the source events as
compressed_atso they won't be re-processed
Events are not deleted when compressed. They stay in the log for forensics and
re-summarization. The context loader just ignores compressed events — it pulls
from memory_entries instead.
API reference
GET /memory?scope=entity&scopeRef=ent_acme&tag=decision&search=ROAS— search curated entriesPOST /memory— create a curated entry (usually from skills)GET / PUT / DELETE /memory/:id— CRUDPOST /memory/events— append a raw event (called by automations, webhooks, call-summary skills)GET /memory/events?scope=&scopeRef=— list raw events
How skills should use memory
The canonical pattern: a skill that processes an external event (a call transcript, an inbound email, a campaign change) should:
- Do its primary job (summarize, analyze, draft a response)
POST /memory/eventswith the structured output as the event body- Optionally
POST /memorydirectly if the output is immediately useful as a curated entry
The Call Summary + Action Items skill in the marketing-agency pack does
exactly this: it writes a call_summary event scoped to the client entity,
giving every future session access to what was said.
Privacy + isolation
Memory lives in the tenant's isolated D1. It never crosses tenant boundaries. The compactor uses the tenant's configured gateway + model routing rules, so the summarization runs under the same DLP and budget policies as any other inference.
Next steps
- Entities & Context — how memory enters the session
- API reference — full endpoint list