Memory
Most agent memory systems are flat files scoped to one user. The agent appends notes, then re-scans them every turn to recall context. That works for a personal assistant. It breaks the moment two agents — or an automation, or another teammate — need to converge on the same fact.
Lobu’s memory layer is entity-based: facts attach to typed entities (Company, Project, Member), so any agent in your org reads the same record.
For a category-level overview of the problem and tradeoffs, see Shared Memory for AI Agents.
How memory is organized
Section titled “How memory is organized” Workspace filesystem Entity memory ───────────────────── ───────────────────── One user, one session Shared across agents Ephemeral (dies with sandbox) Durable (survives sessions) Drafts, scratch files, PDFs Companies, projects, people No schema enforcement Typed with properties + keys
Agent A's DM workspace Company:Acme ├── draft-report.csv ├── CTO departed 2026-03 ├── scratch.py ├── 3 open Linear bugs └── downloaded.pdf ├── DSCR 1.1× (Q1 review) └── Renewal due May 2026A note on a Slack DM filesystem dies with the sandbox. A fact written to Company:Acme survives and shows up the next time any agent — billing, support, CSM — touches Acme.
For the longer argument and where to draw the line between the two, see Filesystem vs Database for Agent Memory.
Entity types
Section titled “Entity types”Entity types define the schema for what you can store. Each type declares a name, description, required fields, and optional metadata properties. You define them in lobu.config.ts:
const company = defineEntityType({ key: "company", name: "Company", description: "A customer or prospect organization", required: ["name"], properties: { name: { type: "string", "x-table-label": "Name", "x-table-column": true }, stage: { type: "string", enum: ["prospect", "active", "churned"] }, arr: { type: "number" }, },});When an agent writes a fact about Acme, Lobu validates the metadata against the Company schema. Required fields must be present. Properties surface in the admin UI and are queryable through the API.
Entity types Entity instances ───────────────────── ───────────────────── defineEntityType({key:"company"}) Company:acme defineEntityType({key:"project"}) Project:checkout-v4 defineEntityType({key:"member"}) Member:daniel
Each type owns the metadata Each instance holds actual data schema (properties, required, conforming to the type's schema eventKinds, measures)Derived entity types
Section titled “Derived entity types”Entity types can be derived — read-only SQL views over your internal tables or an external database connection. Use derived types when the data already lives somewhere and you want to expose it as a queryable entity without copying it:
const invoice = defineEntityType({ key: "invoice", name: "Invoice", backing: { sql: "SELECT id, amount, status, customer_id FROM invoices WHERE status = 'open'", connection: "stripe-db", // optional: run live against external DB },});Relationship types
Section titled “Relationship types”Relationships are typed, directed links between entities. They let you express structure like “Acme owns Project X” or “Daniel is the CSM for Acme”:
const owns = defineRelationshipType({ key: "owns", name: "Owns", description: "A company owns a project.", rules: [{ source: company, target: project }],});rules constrain which entity types can be source and target. This prevents accidental links (e.g. linking two Members to each other as “owns”).
Company:acme ──owns──▸ Project:checkout-v4 Company:acme ──owns──▸ Project:billing-api Member:daniel ──csm-for──▸ Company:acmeRelationships are first-class in the graph — you can query them, attach events to them, and use them to scope what an agent sees.
Event types
Section titled “Event types”Events are the durable facts written to the append-only log. Every event has a semantic type (also called eventKinds) that describes what kind of fact it is. Entity types declare which event kinds are valid for them:
const company = defineEntityType({ key: "company", eventKinds: { "support-ticket": { description: "A customer support interaction", metadataSchema: { type: "object", properties: { ticket_id: { type: "string" }, severity: { type: "string", enum: ["low", "medium", "high", "critical"] }, subject: { type: "string" }, }, }, }, "renewal-update": { description: "Changes to renewal status or terms", }, },});Built-in kinds like observation, decision, and metric are always available. Custom kinds declared on an entity type become subscribable by automations and queryable through the API.
Event log (append-only) ────────────────────────────────────────────────────────── │ id │ semantic_type │ entity_link │ metadata │ │─────│──────────────────│──────────────│────────────────│ │ 101 │ support-ticket │ Company:acme │ severity: high │ │ 102 │ renewal-update │ Company:acme │ stage: active │ │ 103 │ observation │ Company:acme │ namespace: risk│ │ 104 │ decision │ Company:acme │ chose: extend │An event can link to several entities. A single support email can update a company, a contact, and a renewal project while keeping one source event as evidence.
Identity and key fields
Section titled “Identity and key fields”Every entity instance is identified by a key — one to four fields whose values form a stable identity across automation runs. This is how Lobu knows that “Company:acme” written by the CSM automation is the same record as “Company:acme” written by the billing automation.
outputs: { accounts: { entity: company, key: ["domain"], // stable identity — use source IDs, not labels name: ["name"], // optional: readable name fields },} Key fields: ["domain"] Identity across runs ───────────────────── ───────────────────── Run 1: { domain: "acme.com" } ──▸ Company:acme.com Run 2: { domain: "acme.com" } ──▸ Company:acme.com (same record) Run 3: { domain: "initech.com" } ──▸ Company:initech.com (new record)Use durable source IDs, not editable labels. Changing the key fields, their order, or the entity type changes identity and starts a new chain.
For event outputs, key fields work the same way — they determine when a new event supersedes the previous one with the same identity:
outputs: { risk_observations: { event: "observation", key: ["namespace", "entity_domain"], // per-entity risk namespace },}Entity memory in practice
Section titled “Entity memory in practice”A single inbound fact lands on the right entity and accumulates alongside everything else known about that customer:
Inbound fact ────────────────────────────────────────────────────── "Acme's CTO left in March, no exec champion since"
│ ▼
Company:Acme ├── CTO departed 2026-03 — no exec champion ├── Automation: 3 open Linear bugs this week ├── DSCR 1.1× (Q1 review) ├── Renewal due May 2026 └── Decision: declined $2M extension — DSCR below 1.2×
Company:Initech ├── ...Different sources, same destination. A CSM’s note in Slack, an automation ingesting Linear bugs, and a credit decision from a separate agent all converge on Company:Acme. The next agent that reads Acme — through search, recall, or an explicit lookup — sees the full picture without re-deriving it from scratch.
Connectors feed memory
Section titled “Connectors feed memory”Events land in memory through connectors — built-in integrations for GitHub, Gmail, Linear, Stripe, and more, or custom ones your agent writes in TypeScript against the Connector SDK. Connectors run in an isolated V8 sandbox, pull data from APIs, OAuth services, browsers, or webhooks, and produce typed events that automations shape into entity memory.
Automations
Section titled “Automations”Automations are versioned jobs that run on schedules, connector events, or on demand. They separate activation (when to run) from instructions (what to do) from outputs (what to write).
Automation run ────────────────────────────────────────────────────────── Activation: schedule / connector event / workspace event / manual │ ▼ Read: pending window + named sources + bound entities │ ▼ Decide: agent follows prompt + pinned skills │ ▼ Complete: structured output against the window │ ▼ Promote: entity writes, event writes, reactions, notificationsThe prompt is the judgment filter — it tells the agent which signals from a noisy event stream actually matter. Sources decide what it can read; they do not wake it by themselves.
Automation: track-acme-incidents Prompt: "Track changes to active incidents, blockers, and pending PRs. Skip OOO and personal chatter." Schedule: every 1h
9:02 Dan picking up INC-4421, rolling back checkout-v43 9:05 Priya still blocked on checkout cluster admin creds 9:11 Jay caching layer PR ready for review, needs to land by EOD 9:18 Sam OOO today — family thing 9:27 Nina writing INC-4378 postmortem, sharing draft at lunch │ ▼ consolidate
Company:Acme — new memory: ├── Incident INC-4421 — checkout-v43 rollback (Dan) ├── Caching layer PR pending merge by EOD (Jay) └── INC-4378 postmortem drafting (Nina)Sam’s OOO and Nina’s lunch plan drop out — they don’t match the prompt. The agent isn’t online, but the memory is moving. By the time someone asks “what’s going on with Acme?”, the answer is already in the entity.
For the full automation model, see Automations.
When to use entity memory
Section titled “When to use entity memory”Reach for entity memory when you need:
- multiple agents (or automations) to write into and read from the same record
- decisions and context that survive sandboxes and channel resets
- ingested external signal (Linear, HubSpot, Stripe, email) attached to the right customer or project
- operator-visible memory that can be audited and corrected in the UI
If you only need a working directory for one session, the filesystem is enough.
Set it up
Section titled “Set it up”The fastest path is to install the Lobu skill into your coding agent and let it do the wiring — it knows the lobu.config.ts API, plugin install, and CLI flow:
npx skills add lobu-ai/lobu --skill lobuFor the step-by-step manual setup, see the Getting Started guide and the Lobu Memory CLI reference.
Read next
Section titled “Read next”- Automations — scheduled and event-driven jobs over durable context
- Data & tools — built-in and custom connectors that feed memory
- lobu.config.ts reference — declare entity types, relationship types, and automations
- Filesystem vs Database for Agent Memory — where to draw the line