Skip to content
All Stories
2026-11-06•13 min read

Agents · Agent State · Session Log · AI Infrastructure · Durable State · Computed Columns · Embedding Indexes · Versioning · Multimodal AI · Pixeltable

The context architecture for an AI teammate

A proactive AI teammate needs conversations, a searchable inbox copy, curated memory, and versioned drafts. Those are four tables in one Pixeltable catalog, not a SQLite database per session.

Pierre Brunelle

Pierre Brunelle

Pixeltable Team

View as Markdown

Summary: If you were building a proactive AI teammate, the data layer is four tables in one Pixeltable catalog: conversation turns, a searchable copy of inbox and calendar documents, curated memory, and versioned drafts with image, audio, video, and document columns. That is the difference from a SQLite database per session. The teammate reads all four from one catalog, searches the copy instead of calling Gmail on every turn, and re-runs only the columns that a changed prompt, model, or rule actually touches. I ran the schema below on Pixeltable 0.7.24 from PyPI, with a local MiniLM embedding index and no model API key.

The four stores come from Turso's post on Sauna, a work AI teammate. This post maps those stores onto Pixeltable tables. Where Does the Agent Live? makes the companion argument: the agent lives in durable state, not in the sandbox that runs it.

Sauna splits context across four stores#

Sauna is a proactive work AI teammate. It reads Slack, Gmail, calendar, and Granola, and it is built around compounding context. Each repetition adds more of what the team already knows. The numbers and the four-store split below are from the Turso post.

Every session and agent a user starts gets its own Turso database. So does every inbox and calendar they connect. Sauna created over 2 million Turso databases, about 90,000 a day. The architecture has four pieces. Two run on Turso. The Sauna team built the other two in-house:

  1. Conversations. A Turso database per session, holding that chat history.
  2. Synced connections. A Turso database per inbox or calendar, kept in sync, so the teammate searches a local copy instead of calling Gmail on every turn.
  3. Memory system. A two-tier system, in-house, that curates relationships and information about you and the company, and pulls earlier conversations back when a topic returns.
  4. File system. A custom, versioned, searchable store for the drafts, documents, and proposals the agent works on, also in-house.

Isolation is a security boundary. Sharing an inbox with the assistant means sharing that one database, not relying on row filters to keep the rest private. Matthias Pichler, Founding Engineer at Wordware, puts the boundary this way in that post: "a lot easier to reason about and a lot harder to break."

Sauna ran SQLite from the start. The path in the Turso post is Cloudflare Durable Objects SQLite, then Containers plus Litestream, then Turso after May 2026, when Turso removed the per-database active limit on paid plans.

Map those four stores onto one catalog#

Pixeltable is a multimodal backend, not a vector database. The same four concerns become tables in one catalog:

Sauna storeIn Pixeltable
Conversation / session historyA turns table keyed by session_id
Synced inbox or calendarAn inbox table you search, so you are not calling the provider on every turn
Curated memoryA memories table you write on purpose
Versioned drafts and mediaA drafts table with a revision column and pxt.Image, pxt.Audio, pxt.Video, pxt.Document

That is the same catalog boundary as Where Does the Agent Live?. Session log, memory, media, and versions sit in one catalog, each table with its own version history, instead of in five systems. The session log as system of record is Agents as Data. Queryable state versus a dashboard that only replays traces is data plane, not dashboard.

Four tables in one app.py#

Python 3.11 or later. I used 3.12.3. The embedding index in this post runs MiniLM on the machine, so install sentence-transformers as well. You need no API key.

bash

pxt init marks the project root and writes pixeltable.toml. pxt schema update creates the tables. Pass -f in a script. I ran Pixeltable 0.7.24 and sentence-transformers 6.2.0. The shape is the quickstart.

python

A line with a colon is a column you insert. A line with = is a computed column. first_words is a stand-in for a summarizer or a model. Pixeltable records the function by its module path and fills the column on insert. I ran this UDF. I did not call an LLM.

text

pxt schema update loaded app.py in its own process. Inserts from Python need a bind first:

python

Conversation history is rows#

One row is one turn. Isolation is tenant_id and session_id, not a file per chat. That is an agent session log.

python
text
python
text

Alice's filter does not return Bob's calendar question.

Search the copy, not the provider#

Sauna's synced connections exist so the teammate can search mail and events without calling the provider for every lookup. In Pixeltable that copy is rows. I inserted three documents, two for Alice and one for Bob. I did not call Gmail.

python
text

Hugging Face warned that the request was unauthenticated. The insert still finished. The MiniLM weights were already on disk for this run. A first run downloads them if they are not cached. pxt idxs teammate/inbox shows the index on body:

text
python
text

The Gmail copy ranked above the calendar copy for that query. The filter kept Bob's lunch mail out. An embedding index updates on insert. How computed columns and indexes follow the column is in How it works.

Memory is a row you write on purpose#

Conversation history is everything that happened. Memory is the curated subset you want back later: how Alice likes replies written, who owns the budget, which client thread is current. Sauna built that tier in-house. In Pixeltable it is another table in the same catalog.

python
text

A Pydantic AI agent can keep notes in a Pixeltable table the same way: Give a Pydantic AI agent memory and read access to your tables. I did not run that integration here.

Drafts keep revisions and media columns#

Sauna's file system is versioned and searchable, and it is where drafts live. In Pixeltable a draft is a row in a versioned table. Media is a typed column, not a path you copy into object storage by hand. I generated a 64x48 PNG, a one-second sine wav, a one-second gray clip, and a short text file, then inserted them on revision 1. To follow along, put any image, wav, mp4, and text file at these paths.

python
text

pxt describe teammate/drafts printed the media columns as Image | None, Audio | None, Video | None, and Document | None. Collecting the row returned a 64x48 RGB PNG, and file paths for the wav, the mp4, and the text document. A Markdown file failed until I installed mistune, so I used .txt, which Pixeltable already accepts.

Revision 2 is another row, not an overwrite:

python
text

The catalog also versions the table. After the second insert, pxt history teammate/drafts showed schema version 0, then one insert, then another:

text

Isolation is a filter, not a file per chat#

Sauna treats a database as the thing you share or withhold. That is a real advantage when the security story is "this inbox is this file." Pixeltable does not give you one SQLite file per conversation. Isolation is tenant and session scoping: tenant_id, session_id, and the authorization in your server. A runtime key to Pixeltable Cloud does not know Alice from Bob. Authentication and Multi-Tenancy for Pixeltable Apps is the post for that boundary. The tenant_id column records ownership. Your query has to filter on the verified id. That is not row-level security inside the engine.

If the product requirement is a physical database per tenant, and you want that to be the thing you hand to an agent, Turso is built for it. If the product requirement is one versioned catalog for every store, with indexes and recompute, a file per chat is the wrong unit.

A changed rule touches only the column that depends on it#

Change a prompt, a model, or a rule, and only the column that depends on it changes. I changed the Inbox excerpt from 12 words to 4, for every tenant:

python
bash
text

pxt schema update then printed that teammate/inbox.excerpt changed and that stored values were not recomputed. The Gmail excerpt was still the 12-word cut. New rows would get 4 words. Existing rows wait for pxt recompute:

bash
text

After that, Alice's Gmail excerpt was Please send the Q4... and the calendar excerpt was Tuesday 10:00 in the.... Turns, memories, and drafts were not touched. How it works documents that loop. An insert computes new rows, a changed expression applies to new rows, and pxt recompute redoes the existing ones. Swap first_words for a model column and the commands stay the same. I did not run a model column.

Guarded transitions over work in a table are How to build an agent state machine in Pixeltable. Scoring spans as they arrive is Build your own agent trace lake with Pixeltable.

When one SQLite per session is the right tool#

Use Turso when you want tiny isolated SQL documents, sub-millisecond embedded replicas, a database per tenant, an edge runtime, and no multimodal pipeline. That choice, without recapping its feature tables, is Pixeltable vs Turso.

If you don't want a schema, Pixeltable is the wrong tool. If the app needs DATABASE_URL, SQL, branching, or row-level security, and insert should not run a model, that host list is Best serverless Postgres databases for AI apps.

Try it#

Put the four classes in app.py, run pxt schema update app.py teammate -f, and insert a turn. Hosted setup is the Cloud docs. pxt new makes a free hosted database with no account and prints its organization. Put that organization in place of ORG to create the same tables there. I did not run the hosted step.

bash

See also#

Frequently asked questions

What is the context architecture for an AI teammate?

Four stores sit next to the model: conversation history, a searchable copy of connected inboxes and calendars, curated memory, and versioned drafts. In Pixeltable those stores are tables in one catalog, with image, audio, video, and document columns on the drafts table. Isolation is a tenant_id filter, with session_id for turns and drafts, plus the checks in your server, not a physical database per chat.

When is one SQLite database per session the wrong tool?

When conversation, inbox copy, memory, and drafts should live in one catalog, media should be typed columns, or a changed prompt, model, or rule should recompute only the columns that depend on it. Turso remains the better fit for tiny isolated SQL documents, sub-millisecond embedded replicas, a database per tenant, edge runtimes, and no multimodal pipeline.

How does Pixeltable isolate conversations if it is not one database per chat?

Every row carries a tenant_id; turns and drafts also carry a session_id. Queries filter on the verified identity. Pixeltable does not apply row-level security for you. That is application-enforced authorization, described in Authentication and Multi-Tenancy for Pixeltable Apps.

How do you search Gmail or calendar without calling the provider on every turn?

Sync a copy into an inbox table, then search that copy. The listing in this post uses a local MiniLM embedding index on the body column. New rows update the index. I did not call Gmail, Slack, or a calendar API.

Does Pixeltable replace Turso?

No. Pixeltable is a multimodal backend: tables, computed columns, embedding indexes, and media types in one Python file. Turso is distributed SQLite with embedded replicas. Use Turso when you want a database per tenant at the edge. Use Pixeltable when insert should embed, index, and keep drafts next to the session log. Pixeltable vs Turso is the comparison page.

Declarative. Multimodal. Incremental.

One Python file for tables, indexes, and HTTP. Start locally, or sign up for Cloud.

Get started

Docs quickstart · 10-minute tutorial · GitHub · Discord