From d846ad93e897b0f4043c452726cf7d4d68e4c04a Mon Sep 17 00:00:00 2001 From: Pierre Brunelle Date: Thu, 10 Sep 2026 10:36:38 -0700 Subject: [PATCH 1/2] chore: document the migration, scope the library, bump to 0.2.0 PyPI still serves 0.1.0 with an unbounded `pixeltable>=0.3.8`, so a fresh `pip install pixelmemory` resolves Pixeltable 0.7.6 and gets a package whose Document, Audio and Video memories raise on construction. #3 fixed the repo; this is what makes the fix reachable. Adds a "Migrating from 0.1.x" section covering the three caller-visible changes: Python 3.11+, the two renamed splitter parameter dataclasses, and search()/views() replacing the chunk_views/frame_views subscripts. The renames are called out as unavoidable, because those field names are passed straight through to Pixeltable's iterators and so are the API rather than a naming choice. Adds "When not to use pixelmemory". If the schema is fixed when you write the code, TableModel and FastAPIRouter do this natively and the wrapper is not earning anything; it is worth saying so rather than letting someone discover it later. What pixelmemory is actually for is a schema chosen at runtime, plus the multimodal ingest pipelines it wires in the right dependency order. The two grep gates now skip the migration section: a migration guide has to quote the old broken calls to show what replaces them, so scanning it for those calls failed. Verified the gates still fire on a violation injected outside that section. Co-Authored-By: Claude Opus 5 --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/test_search.py | 20 +++++++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2cbbe72..6812d9d 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,59 @@ All examples use the context-based API and are ready to run: **Remember**: Pixelmemory is a reference implementation. Use it as inspiration to build your own memory architecture using [Pixeltable's](https://github.com/pixeltable/pixeltable) flexible primitives. +## Migrating from 0.1.x + +0.2.0 runs on Pixeltable 0.7.6. On 0.5.6+ the Document, Audio and Video +memories raised on construction, so most of 0.1.x did not work. Upgrade with +`pip install -U pixelmemory`. + +Three things changed for callers: + +- **Python 3.11+ is required.** Pixeltable dropped 3.10 in 0.7.2. +- **Two splitter parameter dataclasses are renamed.** `AudioSplitterParams` + takes `duration` / `overlap` / `min_segment_duration` (was + `chunk_duration_sec` / `overlap_sec` / `min_chunk_duration_sec`), and + `DocumentSplitterParams` takes `skip_tags` (was `html_skip_tags`). These + names are passed straight through to Pixeltable's iterators, so they are the + API rather than a naming choice, and the old ones were the bug. +- **`memory.chunk_views[...]` and `memory.frame_views[...]` are replaced by + `memory.search()` and `memory.views()`.** Nothing breaks in practice: + `chunk_views` is a list on `resources`, not a mapping on `Memory`, so those + subscripts always raised `AttributeError`. + +```python +# before -- raised AttributeError +chunk_view = memory.chunk_views["report"] +sim = chunk_view.text.similarity(query) +results = (chunk_view.order_by(sim, asc=False).limit(3) + .select(chunk_view.text, similarity=sim).collect()) + +# after +results = memory.search(query, on="report", limit=3) +``` + +`Memory(...)`, `memory.Entry`, `memory.add()`, the attribute passthrough and +every other `Context` field name are unchanged. + +One behaviour change worth knowing: views and indexes now survive +reconstruction. Building the same `Memory` twice used to drop and rebuild +every view and index, re-running transcription and re-embedding everything. + +## When not to use pixelmemory + +If your memory schema is fixed when you write the code, you do not need a +wrapper. Declare it directly with Pixeltable's `TableModel` and +`FastAPIRouter` and run `pxt schema update` / `pxt service update`; the +[starter kit](https://github.com/pixeltable/pixeltable-starter-kit) has +working apps in that shape. + +pixelmemory earns its keep when the schema is chosen at **runtime** -- when +the `Context` list comes from a config file or a request -- and for the +multimodal ingest pipelines it wires up for you: audio to segments to +transcription to sentences to embeddings, and video to frames plus captions, +in the right dependency order. + + ## License Apache 2.0 License \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4db785c..a73c064 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "pixelmemory" -version = "0.1.0" +version = "0.2.0" description = "Multimodal Memory for AI Agents and MCP." license = "Apache-2.0" readme = "README.md" diff --git a/tests/test_search.py b/tests/test_search.py index 16cad57..e1910c8 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -82,6 +82,22 @@ def test_search_min_score_filters(namespace): assert m.search("cat", min_score=1.1) == [] +def _scannable(path): + """File text with the migration guide stripped out. + + A "Migrating from 0.1.x" section has to quote the old, broken calls to show + what to replace, so scanning it for those calls would always fail. + """ + text = path.read_text() + marker = "## Migrating from 0.1.x" + if marker not in text: + return text + head, rest = text.split(marker, 1) + # resume scanning at the next top-level heading after the migration section + tail = rest.split("\n## ", 1) + return head + (tail[1] if len(tail) > 1 else "") + + def test_no_positional_similarity_in_examples(): """Positional .similarity(query) is deprecated; the keyword form is required. @@ -94,7 +110,7 @@ def test_no_positional_similarity_in_examples(): root = pathlib.Path(__file__).resolve().parent.parent offenders = [] for path in list(root.glob("examples/**/*.py")) + [root / "README.md"]: - for i, line in enumerate(path.read_text().splitlines(), 1): + for i, line in enumerate(_scannable(path).splitlines(), 1): if re.search(r"\.similarity\((?!string=)", line): offenders.append(f"{path.relative_to(root)}:{i}: {line.strip()}") assert not offenders, "positional .similarity() found:\n" + "\n".join(offenders) @@ -108,7 +124,7 @@ def test_examples_do_not_use_chunk_views(): offenders = [ f"{p.relative_to(root)}:{i}" for p in list(root.glob("examples/**/*.py")) + [root / "README.md"] - for i, line in enumerate(p.read_text().splitlines(), 1) + for i, line in enumerate(_scannable(p).splitlines(), 1) if ".chunk_views[" in line or ".frame_views[" in line ] assert not offenders, "stale chunk_views/frame_views access:\n" + "\n".join(offenders) From f2c12938e094decc317607f9708846d94fcf20e5 Mon Sep 17 00:00:00 2001 From: Pierre Brunelle Date: Thu, 10 Sep 2026 10:39:38 -0700 Subject: [PATCH 2/2] fix: regenerate uv.lock for 0.2.0, and make CI check it The version bump left uv.lock recording 0.1.0. CI did not notice, because it ran a bare `uv sync`, which quietly rewrites the lock instead of failing. pixelagent's workflow uses --locked and caught the same drift immediately, so this adopts it: a lockfile that disagrees with pyproject.toml is now a red build rather than a silent fixup. Co-Authored-By: Claude Opus 5 --- .github/workflows/test.yml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ef4f6f..b297352 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: enable-cache: true - name: Install - run: uv sync --python ${{ matrix.python-version }} + run: uv sync --locked --python ${{ matrix.python-version }} - name: Take Pixeltable unpinned # Deliberately ignores the `<0.8` pin in pyproject.toml. This is the canary. diff --git a/uv.lock b/uv.lock index 56f7156..a2f06b3 100644 --- a/uv.lock +++ b/uv.lock @@ -2023,7 +2023,7 @@ wheels = [ [[package]] name = "pixelmemory" -version = "0.1.0" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "pixeltable" },