mem0ai/mem0 · error · Error

S3 Vectors cannot negate this filter shape.

Error message

S3 Vectors cannot negate this filter shape.

What it means

When negating a filter (a NOT condition) for S3 Vectors, the negation helper requires a single-field filter object like { field: { $eq: value } }. It throws when the filter object has zero or multiple top-level entries, because De Morgan-style negation over a compound single-object shape is not representable. Multi-clause shapes must be expressed via explicit $and/$or arrays, which are handled by earlier branches.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/s3_vectors.ts:667

    return { [key]: operators };
  }

  private negateFilter(filter: S3Filter): S3Filter {
    if (Array.isArray(filter.$and)) {
      return {
        $or: filter.$and.map((entry: S3Filter) => this.negateFilter(entry)),
      };
    }

    if (Array.isArray(filter.$or)) {
      return {
        $and: filter.$or.map((entry: S3Filter) => this.negateFilter(entry)),
      };
    }

    const entries = Object.entries(filter);
    if (entries.length !== 1) {
      throw new Error("S3 Vectors cannot negate this filter shape.");
    }

    const [key, rawValue] = entries[0];
    if (key.startsWith("$")) {
      throw new Error("S3 Vectors cannot negate this filter shape.");
    }

    const value =
      typeof rawValue === "object" && rawValue !== null
        ? rawValue
        : { $eq: rawValue };
    const negated: Record<string, any> = {};

    for (const [operator, operand] of Object.entries(value)) {
      switch (operator) {
        case "$eq":
          negated.$ne = operand;
          break;

View on GitHub (pinned to 001c235229)

Solutions

  1. Express multi-condition filters using explicit AND/OR lists so each negated entry contains exactly one field.
  2. Simplify the filter: split the NOT into pre-negated operators (use ne/nin directly instead of negating eq/in).
  3. Move complex negation client-side: query without the NOT clause and post-filter results locally.

Example fix

// before
filters = { NOT: { user_id: "a", org_id: "b" } } // two fields in one negated object

// after
filters = { AND: [ { user_id: { ne: "a" } }, { org_id: { ne: "b" } } ] }
Defensive patterns

Strategy: validation

Validate before calling

function assertSingleFieldLeaves(filters: any): void {
  for (const [k, v] of Object.entries(filters ?? {})) {
    if (["AND","OR"].includes(k)) { (v as any[]).forEach(assertSingleFieldLeaves); continue; }
    if (k === "NOT") { assertSingleFieldLeaves(v); continue; }
    if (v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 1)
      throw new Error('Each filter leaf must contain exactly one field');
  }
}
assertSingleFieldLeaves(filters);

Type guard

const isSingleFieldFilter = (f: Record<string, unknown>): boolean => Object.keys(f).length === 1 && !Object.keys(f)[0].startsWith('$');

Try / catch

try { await memory.search(q, { filters }); } catch (e) { if (e instanceof Error && e.message === 'S3 Vectors cannot negate this filter shape.') { /* split NOT into AND of ne/nin */ } else throw e; }

Prevention

When it happens

Trigger: A filters object that negates into a plain object with more than one key, e.g. NOT over { {a:1}, {b:2} } collapsed into one object instead of an $and array; or an empty filter object {} reaching negateFilter via a NOT construct.

Common situations: Complex nested filters combining AND/OR/NOT produced by LLM filter extraction or generic filter builders; filters that worked on other stores but structurally collapse into multi-key objects when converted for S3 Vectors.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/f071c7fa6d2e2491. Report an issue: GitHub.