Keeps a database and a set of text files in sync so AI agents always start a session with accurate, up-to-date state. No opinions about your schema, your agents, or your file format.
Renders DB rows into agent-readable text files, watches for changes, and re-renders automatically. One call to db.watch() keeps everything current.
Per-entity file trees via defineEntityContext(). One subdirectory per row, one file per relationship — with orphan cleanup, budget limits, and combined context files.
Ingest agent-written output back into the database. defineWriteback() watches output files, calls your parse() function, and persists entries — with deduplication and offset tracking.
Declare your entire schema in a lattice.config.yml file. Use lattice generate to produce TypeScript types and SQL migrations. Zero boilerplate.
LLM context windows are ephemeral. Your application state lives in a database. Every agent session starts cold unless something bridges them. Lattice is that bridge.
Your DB (SQLite)
│ Lattice reads rows → render → text
▼
Context files (Markdown, JSON, etc.)
│ LLM agents read these at session start
▼
Agent output files
│ Lattice writeback parses these
▼
Your DB (rows inserted/updated)Lattice never modifies your existing rows — it only reads for rendering and appends via the writeback pipeline.
import { Lattice } from 'latticesql';
const db = new Lattice('./state.db');
db.define('agents', {
columns: {
id: 'TEXT PRIMARY KEY',
name: 'TEXT NOT NULL',
persona: 'TEXT',
active: 'INTEGER DEFAULT 1',
},
render(rows) {
return rows
.filter((r) => r.active)
.map((r) => `## ${r.name}\n\n${r.persona ?? ''}`)
.join('\n\n---\n\n');
},
outputFile: 'AGENTS.md',
});
await db.init();
await db.insert('agents', { name: 'Alpha', persona: 'Research assistant.' });
// Render DB → context files
await db.render('./context');
// Writes: context/AGENTS.md
// Watch for changes, re-render every 5 seconds
const stop = await db.watch('./context', { interval: 5000 });One directory per entity. One file per relationship. Agents load exactly what they need — no giant context dumps.
db.defineEntityContext('agents', {
slug: (row) => row.slug as string,
index: {
outputFile: 'agents/AGENTS.md',
render: (rows) => rows.map((r) => `- ${r.name}`).join('\n'),
},
files: {
'AGENT.md': {
source: { type: 'self' },
render: ([r]) => `# ${r.name}\n\n${r.bio ?? ''}`,
},
'TASKS.md': {
source: { type: 'hasMany', table: 'tasks', foreignKey: 'agent_id' },
render: (rows) => rows.map((r) => `- ${r.title}`).join('\n'),
omitIfEmpty: true,
},
},
combined: { outputFile: 'CONTEXT.md', exclude: [] },
protectedFiles: ['SESSION.md'],
});
// Produces per-agent directories:
// context/agents/alpha/AGENT.md
// context/agents/alpha/TASKS.md
// context/agents/alpha/CONTEXT.mdcontext/
├── agents/
│ └── AGENTS.md ← global index
├── agents/alpha/
│ ├── AGENT.md
│ ├── TASKS.md ← omitted when empty
│ └── CONTEXT.md ← all files combined
└── agents/beta/
├── AGENT.md
└── CONTEXT.mdAlso included
reconcile() removes orphaned directories when entities are deletedlattice generateGenerate TypeScript types and SQL migration from YAML config
lattice renderOne-shot render — write all context files from the database
lattice reconcileRender + orphan cleanup — remove stale entity directories
lattice statusDry-run reconcile — preview what would change
lattice watchPoll the DB and re-render on every interval
Define your schema, write your render functions, and Lattice handles the rest. Works with any agent framework, any file format.