Join our live webinar on November 19. Laís Carvalho from Pydantic and I will walk through the agent in this post live, on Thursday, November 19, at 11:30am ET. Register for the webinar.
With the Pixeltable capability and memory store in Pydantic AI Harness 0.66.0, you can now build an agentic system that searches your media files (images, audio, video, and documents) and keeps notes across runs.
Why passing files in the prompt stops working#
Pydantic AI agents can take an image, audio clip, video, or PDF in the prompt when you already know which file belongs there. An application holding thousands of photos, recordings, or PDFs has a different problem: the agent has to find the right files by what they show or say, and keep finding new uploads as they arrive.
In the usual stack, a file lands in object storage, a background worker writes descriptions or transcripts, a sync script loads a separate vector database, and any file the worker has not reached yet is missing from the agent's answers.
Open-source Pixeltable combines the table, derived columns, and embedding index in one file. You declare a TableModel with media columns, computed columns, and embedding indexes, and Pixeltable updates them when rows are inserted. Harness 0.66.0 adds two capabilities: Pixeltable gives the agent four read-only catalog tools (list_tables, describe_table, query_table, and similarity_search) that return rows without exposing media file paths, and Memory(PixeltableMemoryStore(...)) stores agent memory in a Pixeltable table in the same catalog.
How the marketplace agent is wired#
In the marketplace example app, sellers upload three photos and a price per listing, and buyers ask a Pydantic AI agent for recommendations. Two Pixeltable tables back the agent: market.listings holds the photos, extracted metadata, and image embedding index, and market.memory stores buyer preferences between separate runs.
The agent lives in agent.py. Below is an excerpt from that file (not a standalone script: the top of agent.py imports MODEL, Listings, and TableModel from app.py, defines the Picks output schema, binds the catalog with TableModel.bind_all('market'), and attaches an output_validator that verifies returned IDs and prices against market.listings):
The agent works with any model provider Pydantic AI supports. In the marketplace app, MODEL is 'gemini-3.8-flash', and the model calls that extract titles, categories, conditions, and photo embeddings run when a listing is inserted rather than when the agent searches. Filtering rows with query_table calls no model at all.
Search images by what they show#
When an embedding index sits directly on an image column, the agent searches what is inside the photo rather than relying on manually written captions or tags. This excerpt from app.py shows the indexed image column on Listings (TableModel = pxt.model_base(), pxtf, EMBEDDING_MODEL = 'gemini-embedding-2', and the derived Gemini columns including title and condition sit in the rest of the file):
When a buyer asks for reading light under $150, the agent inspects the table schema with describe_table, runs similarity_search on photo_1 with search phrases of its own, filters candidates, and returns structured JSON (listing IDs, titles, and prices) so the frontend loads photos by ID without ever passing image files through tool context:
similarity_search calls and returns listing IDs and prices; the app fetches the photos by ID.You choose the embedding function per column. Local models such as sentence-transformers and CLIP from Hugging Face run with no API key. Hosted models from OpenAI, Gemini, Voyage AI, or Twelve Labs use your API key, and each similarity_search call embeds its query string once. A single user request can make several similarity_search calls (Figure 3 shows two), which means multiple embedding API calls on hosted indexes.
Pixeltable's pxtf.gemini.embed_content and pxtf.twelvelabs.embed functions also accept audio and video columns directly. For timestamped search over longer clips, Pydantic's video example takes a text-based route: it transcribes 30-second segments of audio, captions one frame every 10 seconds, and indexes that text so search results include frame timestamps.
New files become searchable once insert finishes#
Pixeltable maintains computed columns and embedding indexes incrementally on insert. When you add a row, Pixeltable computes every derived column and updates the index as part of that insert operation, with no external cron job or indexing queue to trigger.
How long an insert takes depends on the model calls and media transforms defined on the table. Adding a listing in the marketplace app runs Gemini extraction on three photos, bounding-box cropping with Pillow, speech synthesis and an 11-second ffmpeg video, plus the embedding call on photo_1. app.py runs POST /listings as a background job because those calls take around 15 seconds in typical runs (23 seconds for the recorded insert behind Figure 4). When I posted a 19th listing (a floor lamp at $120) against the running app, once Gemini finished writing the details and the embedding index updated, running the same buyer query again returned the new floor lamp.
Because computed media columns live in the same table as the search index, the same insert also generated the app's user-facing assets in that row: the cropped thumbnail from the custom crop_to_box UDF and the narrated listing video:
Filter on structured fields a model wrote#
similarity_search ranks listings by visual or semantic closeness, while query_table applies equality filters. Filtering on model-written descriptions only stays reliable if those values come from a constrained schema rather than free text that mixes "like new", "like-new", and "barely used".
In app.py, Pixeltable asks Gemini for JSON matching a Pydantic model and stores details = response.parsed.astype(pxt.Json[ListingDetails]), promoting fields such as condition into typed columns indexed with a BtreeIndex (an insert with a missing field or invalid enum value fails cleanly rather than storing dirty strings):
Because condition is restricted to four literal strings, the agent can call query_table on condition == 'like new' without guessing phrasing variants.
Keep notes across separate runs with PixeltableMemoryStore#
PixeltableMemoryStore implements Harness's MemoryStore interface on top of a Pixeltable table (market.memory here), alongside the built-in in-memory, file, SQLite, and Postgres backends. On a write, the agent calls write_memory; on the first request of any later run, Memory injects an excerpt of the saved notes into context even when there is no shared conversation history. Because writes use compare-and-set versions and operation receipts, a retried tool call replays instead of duplicating a note (see the technical walkthrough for receipt details and version history behavior: deleted memories stay in prior table versions unless you drop the table).
I ran agent.py as two separate processes with no chat history between them. In run 1, telling the agent "I only buy things in like-new condition. Remember that." triggered a write_memory call. In run 2, asking "What do you have for my reading corner?" still matched the $120 brass floor lamp first on visual similarity, but the agent applied the saved preference with query_table and returned only the three like-new lamps:
write_memory in process 1 filters search results in process 2, omitting the good floor lamp even though it ranked first by similarity.Where this is the wrong tool#
If your agent only inspects a few known files passed directly in a request, use Pydantic AI's native multimodal inputs instead of setting up a Pixeltable table. If you only want persistent agent notes and have no media tables or embedding indexes beside them, use Harness's built-in FileStore, SqliteMemoryStore, or PostgresMemoryStore. If you do not want a typed TableModel schema, or you already operate a vector database and ingest pipeline you plan to keep, Pixeltable is the wrong tool.
Run the example or wire your own agent#
To run the complete marketplace demo yourself (requires Python 3.12 to 3.14, uv, ffmpeg, and GEMINI_API_KEY set in your environment), clone the repository, apply the schema and start the local FastAPI service, seed the sample listings using the printed service URL, and query the agent:
If you are adding the integration directly to your own Pydantic AI project instead of cloning the demo, install the extra alongside a model provider package (plus pixeltable[serve] if you want Pixeltable's FastAPI serving routes):
The same app.py runs hosted on Pixeltable Cloud via pxt db update when you are ready to move off a local machine.
See it live on November 19. Laís Carvalho from Pydantic and I will walk through this agent at 11:30am ET. Register for the webinar.
Resources#
- pixeltable-marketplace repository (full
app.py,agent.py, sample photos, and seed scripts) - Give a Pydantic AI agent memory and read access to your tables (step-by-step snippet reference with outputs on the same
0.66.0release) - Pixeltable docs for Pydantic AI Harness
- Pydantic AI Harness guide to the Pixeltable capability



