> ## Documentation Index
> Fetch the complete documentation index at: https://honcho.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Using Filters

> Learn how to filter workspaces, peers, sessions, and messages using Honcho's powerful filtering system

Honcho provides a sophisticated filtering system that allows you to query workspaces, peers, sessions, and messages with precise control. The filtering system supports logical operators, comparison operators, metadata filtering, and wildcards to help you find exactly what you need.

## Basic Filtering Concepts

Filters in Honcho are expressed as dictionaries that define conditions for matching resources. The system supports both simple equality filters and complex queries with multiple conditions.

### Simple Filters

The most basic filters check for exact matches:

```python Python theme={null}
from honcho import Honcho

# Initialize client
honcho = Honcho()

# Simple peer filter
peers = honcho.peers(filters={"peer_id": "alice"})

# Simple session filter with metadata
sessions = honcho.sessions(filters={
    "metadata": {"type": "support"}
})

# Simple message filter
messages = honcho.messages(filters={
    "session_id": "support-chat-1",
    "peer_id": "alice"
})
```

```typescript TypeScript theme={null}
import { Honcho } from "@honcho-ai/sdk";

(async () => {
    // Initialize client
    const honcho = new Honcho({});

    // Simple peer filter
    const peers = await honcho.peers({
      filters: { peer_id: "alice" }
    });

    // Simple session filter with metadata
    const sessions = await honcho.sessions({
      filters: {
        metadata: { type: "support" }
      }
    });

    // Simple message filter
    const messages = await honcho.messages({
      filters: {
        session_id: "support-chat-1",
        peer_id: "alice"
      }
    });
})();
```

## Logical Operators

Combine multiple conditions using logical operators for complex queries:

### AND Operator

Use AND to require all conditions to be true:

```python Python theme={null}
messages = honcho.messages(filters={
    "AND": [
        {"session_id": "chat-1"},
        {"created_at": {"gte": "2024-01-01"}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    const messages = await honcho.messages({
      filters: {
        AND: [
          { session_id: "chat-1" },
          { created_at: { gte: "2024-01-01" } }
        ]
      }
    });
})();
```

### OR Operator

Use OR to match any of the specified conditions:

```python Python theme={null}
# Find messages from either alice or bob
messages = session.messages(filters={
    "OR": [
        {"peer_id": "alice"},
        {"peer_id": "bob"}
    ]
})

# Complex OR with metadata conditions
sessions = honcho.sessions(filters={
    "OR": [
        {"metadata": {"priority": "high"}},
        {"metadata": {"urgent": True}},
        {"metadata": {"escalated": True}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find messages from either alice or bob
    const messages = await session.messages({
      filters: {
        OR: [
          { peer_id: "alice" },
          { peer_id: "bob" }
        ]
      }
    });

    // Complex OR with metadata conditions
    const sessions = await honcho.sessions({
      filters: {
        OR: [
          { metadata: { priority: "high" } },
          { metadata: { urgent: true } },
          { metadata: { escalated: true } }
        ]
      }
    });
})();
```

### NOT Operator

Use NOT to exclude specific conditions:

```python Python theme={null}
# Find all peers except alice
peers = honcho.peers(filters={
    "NOT": [
        {"peer_id": "alice"}
    ]
})

# Find sessions that are NOT completed
sessions = honcho.sessions(filters={
    "NOT": [
        {"metadata": {"status": "completed"}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find all peers except alice
    const peers = await honcho.peers({
      filters: {
        NOT: [
          { peer_id: "alice" }
        ]
      }
    });

    // Find sessions that are NOT completed
    const sessions = await honcho.sessions({
      filters: {
        NOT: [
          { metadata: { status: "completed" } }
        ]
      }
    });
})();
```

### Negation and Unset Fields

A field can be unset, and negation has to account for it. `NOT` and the `ne`
comparison operator both **include** rows where the field has no value at all: a
field with no value is not the value you are excluding, so excluding that value
keeps the row.

Positive conditions work the other way around. An unset field matches nothing,
so equality and `contains` never return those rows. To select them, filter on
`null` directly:

| Filter | Rows where the field is unset |
| - | - |
| `{"field": "x"}`, `{"field": {"contains": "x"}}` | Excluded |
| `{"NOT": [{"field": "x"}]}`, `{"field": {"ne": "x"}}` | Included |
| `{"field": null}` | Only these |
| `{"field": {"ne": null}}` | Excluded — the field must have some value |

Of the filterable fields, only a conclusion's `session_id` can be unset: a
conclusion drawn across a whole workspace belongs to no single session. Every
other field is always populated, so none of this affects filters on them. See
[Filtering Conclusions](#filtering-conclusions) for what conclusions are.

```python Python theme={null}
# Every conclusion except the ones in this session — including
# workspace-level conclusions, which belong to no session at all
conclusions = peer.conclusions.list(filters={
    "NOT": [
        {"session_id": "session-123"}
    ]
})

# Equivalent
conclusions = peer.conclusions.list(filters={
    "session_id": {"ne": "session-123"}
})
```

```typescript TypeScript theme={null}
(async () => {
    // Every conclusion except the ones in this session — including
    // workspace-level conclusions, which belong to no session at all
    const conclusions = await peer.conclusions.list({
      filters: { NOT: [{ session_id: "session-123" }] }
    });

    // Equivalent
    const same = await peer.conclusions.list({
      filters: { session_id: { ne: "session-123" } }
    });
})();
```

To exclude a value **and** require the field to be set, combine the two with
`AND`:

```python Python theme={null}
# Excludes workspace-level conclusions and `session-123`
conclusions = peer.conclusions.list(filters={
    "AND": [
        {"session_id": {"ne": "session-123"}},
        {"session_id": {"ne": None}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Excludes workspace-level conclusions and `session-123`
    const conclusions = await peer.conclusions.list({
      filters: {
        AND: [
          { session_id: { ne: "session-123" } },
          { session_id: { ne: null } }
        ]
      }
    });
})();
```

### Combining Logical Operators

Create sophisticated queries by combining different logical operators:

```python Python theme={null}
# Find messages from alice OR bob, but NOT where message has archived set to true in metadata
messages = session.messages(filters={
    "AND": [
        {
            "OR": [
                {"peer_id": "alice"},
                {"peer_id": "bob"}
            ]
        },
        {
            "NOT": [
                {"metadata": {"archived": True}}
            ]
        }
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find messages from alice OR bob, but NOT where message has archived set to true in metadata
    const messages = await session.messages({
      filters: {
        AND: [
          {
            OR: [
              { peer_id: "alice" },
              { peer_id: "bob" }
            ]
          },
          {
            NOT: [
              { metadata: { archived: true } }
            ]
          }
        ]
      }
    });
})();
```

## Comparison Operators

Use comparison operators for range queries and advanced matching:

### Numeric Comparisons

```python Python theme={null}
# Find sessions created after a specific date
sessions = honcho.sessions(filters={
    "created_at": {"gte": "2024-01-01"}
})

# Find messages within a date range
messages = session.messages(filters={
    "created_at": {
        "gte": "2024-01-01",
        "lte": "2024-12-31"
    }
})

# Metadata numeric comparisons
sessions = honcho.sessions(filters={
    "metadata": {
        "score": {"gt": 8.5},
        "duration": {"lte": 3600}
    }
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find sessions created after a specific date
    const sessions = await honcho.sessions({
      filters: {
        created_at: { gte: "2024-01-01" }
      }
    });

    // Find messages within a date range
    const messages = await session.messages({
      filters: {
        created_at: {
          gte: "2024-01-01",
          lte: "2024-12-31"
        }
      }
    });

    // Metadata numeric comparisons
    const filteredSessions = await honcho.sessions({
      filters: {
        metadata: {
          score: { gt: 8.5 },
          duration: { lte: 3600 }
        }
      }
    });
})();
```

### List Membership

A bare list is shorthand for `in`, so `{"peer_id": ["alice", "bob"]}` and
`{"peer_id": {"in": ["alice", "bob"]}}` are equivalent:

```python Python theme={null}
# Shorthand: a bare list means "any of these"
messages = session.messages(filters={
    "peer_id": ["alice", "bob", "charlie"]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Shorthand: a bare list means "any of these"
    const messages = await session.messages({
      filters: { peer_id: ["alice", "bob", "charlie"] }
    });
})();
```

> [!WARNING]
>
> Bare lists behave differently inside metadata — use `{"in": [...]}` there for OR matching.

The explicit form, plus the other comparison operators:

```python Python theme={null}
# Find messages from specific peers in a session
messages = session.messages(filters={
    "peer_id": {"in": ["alice", "bob", "charlie"]}
})

# Find sessions with specific tags
sessions = honcho.sessions(filters={
    "metadata": {
        "tag": {"in": ["important", "urgent", "follow-up"]}
    }
})

# Not equal comparisons
peers = honcho.peers(filters={
    "metadata": {
        "status": {"ne": "inactive"}
    }
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find messages from specific peers in a session
    const messages = await session.messages({
      filters: {
        peer_id: { in: ["alice", "bob", "charlie"] }
      }
    });

    // Find sessions with specific tags
    const sessions = await honcho.sessions({
      filters: {
        metadata: {
          tag: { in: ["important", "urgent", "follow-up"] }
        }
      }
    });

    // Not equal comparisons
    const peers = await honcho.peers({
      filters: {
        metadata: {
          status: { ne: "inactive" }
        }
      }
    });
})();
```

## Metadata Filtering

Metadata filtering is particularly powerful in Honcho, supporting nested conditions and complex queries:

### Basic Metadata Filtering

```python Python theme={null}
# Simple metadata equality
sessions = honcho.sessions(filters={
    "metadata": {
        "type": "customer_support",
        "priority": "high"
    }
})

# Nested metadata objects
peers = honcho.peers(filters={
    "metadata": {
        "profile": {
            "role": "admin",
            "department": "engineering"
        }
    }
})
```

```typescript TypeScript theme={null}
(async () => {
    // Simple metadata equality
    const sessions = await honcho.sessions({
      filters: {
        metadata: {
          type: "customer_support",
          priority: "high"
        }
      }
    });

    // Nested metadata objects
    const peers = await honcho.peers({
      filters: {
        metadata: {
          profile: {
            role: "admin",
            department: "engineering"
          }
        }
      }
    });
})();
```

### Advanced Metadata Queries

> [!NOTE]
>
> If you want to do advanced queries like these, make sure not to create metadata fields that use the same names as the included comparison operators! For example, if you have a metadata field called `contains`, it will conflict with the `contains` operator.

```python Python theme={null}
# Metadata with comparison operators
sessions = honcho.sessions(filters={
    "metadata": {
        "score": {"gte": 4.0, "lte": 5.0},
        "created_by": {"ne": "system"},
        "tags": {"contains": "important"}
    }
})

# Complex metadata conditions
messages = session.messages(filters={
    "AND": [
        {"metadata": {"sentiment": {"in": ["positive", "neutral"]}}},
        {"metadata": {"confidence": {"gt": 0.8}}},
        {"content": {"icontains": "thank"}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Metadata with comparison operators
    const sessions = await honcho.sessions({
      filters: {
        metadata: {
          score: { gte: 4.0, lte: 5.0 },
          created_by: { ne: "system" },
          tags: { contains: "important" }
        }
      }
    });

    // Complex metadata conditions
    const messages = await session.messages({
      filters: {
        AND: [
          { metadata: { sentiment: { in: ["positive", "neutral"] } } },
          { metadata: { confidence: { gt: 0.8 } } },
          { content: { icontains: "thank" } }
        ]
      }
    });
})();
```

## Wildcards

Use wildcards (\*) to match any value for a field:

```python Python theme={null}
# Find all sessions with any peer_id (essentially all sessions)
sessions = honcho.sessions(filters={
    "peer_id": "*"
})

# Wildcard in lists - matches everything
messages = session.messages(filters={
    "peer_id": {"in": ["alice", "bob", "*"]}
})

# Metadata wildcards
sessions = honcho.sessions(filters={
    "metadata": {
        "type": "*",  # Any type
        "status": "active"  # But status must be active
    }
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find all sessions with any peer_id (essentially all sessions)
    const sessions = await honcho.sessions({
      filters: {
        peer_id: "*"
      }
    });

    // Wildcard in lists - matches everything
    const messages = await session.messages({
      filters: {
        peer_id: { in: ["alice", "bob", "*"] }
      }
    });

    // Metadata wildcards
    const filteredSessions = await honcho.sessions({
      filters: {
        metadata: {
          type: "*",  // Any type
          status: "active"  // But status must be active
        }
      }
    });
})();
```

A wildcard, like an empty filter `{}`, places no constraint, so it is always true, including inside logical operators:

| Filter | Matches |
| - | - |
| `{"AND": [{"peer_id": "*"}, {"peer_id": "bob"}]}` | bob only |
| `{"OR": [{"peer_id": "*"}, {"peer_id": "bob"}]}` | everything |
| `{"NOT": [{"peer_id": "*"}]}` | nothing |
| `{"NOT": [{}]}` | nothing |

## Resource-Specific Examples

### Filtering Workspaces

```python Python theme={null}
# Find workspaces by name pattern
workspaces = honcho.workspaces(filters={
    "name": {"contains": "prod"}
})

# Filter by metadata
workspaces = honcho.workspaces(filters={
    "metadata": {
        "environment": "production",
        "team": {"in": ["backend", "frontend", "devops"]}
    }
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find workspaces by name pattern
    const workspaces = await honcho.workspaces({
      filters: {
        name: { contains: "prod" }
      }
    });

    // Filter by metadata
    const workspaces = await honcho.workspaces({
      filters: {
        metadata: {
          environment: "production",
          team: { in: ["backend", "frontend", "devops"] }
        }
      }
    });
})();
```

### Filtering Messages

```python Python theme={null}
# Find error messages from the last week
from datetime import datetime, timedelta

week_ago = (datetime.now() - timedelta(days=7)).isoformat()
messages = session.messages(filters={
    "AND": [
        {"content": {"icontains": "error"}},
        {"created_at": {"gte": week_ago}},
        {"metadata": {"level": {"in": ["error", "critical"]}}}
    ]
})

# Find messages in specific sessions with sentiment analysis
messages = session.messages(filters={
    "AND": [
        {"session_id": {"in": ["support-1", "support-2", "support-3"]}},
        {"metadata": {"sentiment": "negative"}},
        {"metadata": {"confidence": {"gte": 0.7}}}
    ]
})
```

```typescript TypeScript theme={null}
(async () => {
    // Find error messages from the last week
    const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
    const messages = await session.messages({
      filters: {
        AND: [
          { content: { icontains: "error" } },
          { created_at: { gte: weekAgo } },
          { metadata: { level: { in: ["error", "critical"] } } }
        ]
      }
    });

    // Find messages in specific sessions with sentiment analysis
    const sentimentMessages = await session.messages({
      filters: {
        AND: [
          { session_id: { in: ["support-1", "support-2", "support-3"] } },
          { metadata: { sentiment: "negative" } },
          { metadata: { confidence: { gte: 0.7 } } }
        ]
      }
    });
})();
```

### Filtering Conclusions

Conclusions belong to an observer/observed peer pair (accessed via
`peer.conclusions` for self-conclusions or `peer.conclusions_of(target)` for
conclusions about another peer). The observer and observed are filled in
automatically by the scope, so the `filters` you pass add to them.

The most useful conclusion-specific field is `level`, the reasoning level:

* `explicit` — extracted directly from messages
* `deductive` / `inductive` / `contradiction` — derived later during dreaming

A common request is to surface only the directly-stated facts and exclude
anything inferred during dreaming — filter `level` to `explicit`:

```python Python theme={null}
# Only conclusions extracted directly from messages (exclude dream-derived)
explicit = peer.conclusions.list(filters={"level": "explicit"})

# Only dream-derived conclusions
derived = peer.conclusions.list(filters={"level": {"in": ["deductive", "inductive"]}})

# Same filtering on semantic search
results = peer.conclusions.query(
    "food preferences",
    filters={"level": "deductive"},
)

# Conclusions about another peer, explicit only
bob_explicit = peer.conclusions_of("bob").list(filters={"level": "explicit"})
```

```typescript TypeScript theme={null}
(async () => {
    // Only conclusions extracted directly from messages (exclude dream-derived)
    const explicit = await peer.conclusions.list({ filters: { level: "explicit" } });

    // Only dream-derived conclusions
    const derived = await peer.conclusions.list({
      filters: { level: { in: ["deductive", "inductive"] } }
    });

    // Same filtering on semantic search (query, topK, distance, filters)
    const results = await peer.conclusions.query(
      "food preferences",
      10,
      undefined,
      { level: "deductive" }
    );

    // Conclusions about another peer, explicit only
    const bobExplicit = await peer.conclusionsOf("bob").list({
      filters: { level: "explicit" }
    });
})();
```

Two fields link conclusions to what they came from. `source_ids` holds the
conclusions a derived one was reasoned from; `source_message_ids` holds the
messages an explicit one cites as evidence. Both take `contains` for a single
id, `in` for any of several, and a bare list to require every id listed:

```python Python theme={null}
# Conclusions reasoned from a given conclusion
built_on = peer.conclusions.list(filters={"source_ids": {"contains": conclusion_id}})

# Conclusions that cite a given message as evidence
from_message = peer.conclusions.list(
    filters={"source_message_ids": {"contains": message_id}}
)
```

```typescript TypeScript theme={null}
(async () => {
    // Conclusions reasoned from a given conclusion
    const builtOn = await peer.conclusions.list({
      filters: { source_ids: { contains: conclusionId } }
    });

    // Conclusions that cite a given message as evidence
    const fromMessage = await peer.conclusions.list({
      filters: { source_message_ids: { contains: messageId } }
    });
})();
```

`source_message_ids` is recorded only for conclusions the deriver wrote on
Honcho v3.5.0 or later. Every derived level returns it as null, and so do older
explicit conclusions until the deriver derives the same conclusion again and
merges the new citations into them.

## Value Types

A filter value has to be usable against the field it targets. Honcho validates
this before running the query and returns a `422` with an explanation when it
doesn't hold, rather than failing mid-query or quietly returning nothing.

| Field | Accepts |
| - | - |
| Text — `peer_id`, `session_id`, `id`, `content` | Strings |
| Numeric — `token_count` | Numbers, or numeric strings like `"5"`. Exact for integers of any size |
| Timestamps — `created_at` | ISO 8601 strings such as `"2026-01-01"` or `"2026-01-01T12:00:00Z"` |
| Boolean — `is_active` | `true` / `false` |
| `metadata` | An object, matched by containment — bare or under `contains` |
| Fields with fixed values — `level` | One of the documented values |
| Any field | `null`, which matches rows where the field is unset |

Three consequences worth knowing:

* **Booleans must be real booleans.** `{"is_active": True}` filters; the string
  `{"is_active": "true"}` is rejected.
* **Fixed-value fields are checked.** `{"level": "explicit"}` filters;
  `{"level": "typo"}` is rejected instead of returning an empty list, so a
  misspelling doesn't look like "no results".
* **`metadata` takes only the two shapes above** — bare, or under `contains`.
  Comparison operators don't apply to the object as a whole, so
  `{"metadata": {"ne": {...}}}` is rejected. To compare *within* metadata, put
  the operator on the key — `{"metadata": {"status": {"ne": "done"}}}`. To negate
  a match, wrap the whole condition in `NOT`. See
  [Metadata Filtering](#metadata-filtering).

Numeric comparisons within metadata (for example,
`{"metadata": {"score": {"gt": 0}}}`) compare stored native JSON numbers and finite
numeric strings. Native numbers retain PostgreSQL's full JSONB numeric range.
Stored strings are conservatively limited to 1,000 characters and at most four
exponent digits to avoid overflow during casting; strings such as `"NaN"`, `"Infinity"`, and
`"-Infinity"` are intentionally not treated as numbers. Non-numeric or out-of-bound
strings do not match ordered numeric comparisons (`gt`, `gte`, `lt`, `lte`);
`ne` still matches values distinct from the requested number, including non-numeric
and missing values.

For every field other than `metadata`, the same rules apply however the value is
wrapped — bare, under an operator, or inside an `in` list — so
`{"level": "explicit"}`, `{"level": {"ne": "explicit"}}` and
`{"level": {"in": ["explicit"]}}` all validate identically.

An empty `in` list matches nothing:

```python Python theme={null}
# Returns no results — an empty allowlist excludes everything
messages = session.messages(filters={"peer_id": {"in": []}})
```

```typescript TypeScript theme={null}
(async () => {
    // Returns no results — an empty allowlist excludes everything
    const messages = await session.messages({ filters: { peer_id: { in: [] } } });
})();
```

## Scoping Recall to Sessions

The [chat endpoint](https://honcho.dev/docs/v3/documentation/features/chat.md) and the representation
endpoint accept a `filters` body too, but a deliberately narrow one: it defines
a **session allowlist**, restricting what the request can recall to the sessions
you name — conclusions on both endpoints, and on chat the messages the agent
reads as well.

This is how you restrict recall to more than one session. The `session_id`
parameter pins a request to exactly one session; an allowlist accepts a set.

Only the `session_id` key is supported here, in three shapes:

```json theme={null}
{"filters": {"session_id": "support-chat-1"}}
{"filters": {"session_id": ["support-chat-1", "support-chat-2"]}}
{"filters": {"session_id": {"in": ["support-chat-1", "support-chat-2"]}}}
```

```bash Chat theme={null}
curl -X POST "$HONCHO_URL/v3/workspaces/my-app/peers/user-123/chat" \
  -H "Authorization: Bearer $HONCHO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What did the user ask about billing?",
    "filters": { "session_id": ["support-chat-1", "support-chat-2"] }
  }'
```

```bash Representation theme={null}
curl -X POST "$HONCHO_URL/v3/workspaces/my-app/peers/user-123/representation" \
  -H "Authorization: Bearer $HONCHO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": { "session_id": ["support-chat-1", "support-chat-2"] }
  }'
```

Both SDKs expose this as a `sessions` option, which goes on the wire as the
`filters` body above:

```python Python theme={null}
answer = user.chat("What did the user ask about billing?",
                   sessions=["support-chat-1", "support-chat-2"])

rep = user.representation(sessions=["support-chat-1", "support-chat-2"])
```

```typescript TypeScript theme={null}
const answer = await user.chat("What did the user ask about billing?", {
  sessions: ["support-chat-1", "support-chat-2"],
});

const rep = await user.representation({
  sessions: ["support-chat-1", "support-chat-2"],
});
```

> [!NOTE]
>
> If the same set of sessions is a boundary you reuse, name it: a
> [scope](https://honcho.dev/docs/v3/documentation/features/advanced/scopes.md) is a persistent version of
> this allowlist, and querying a single scope recalls at full depth rather than
> `explicit`-only. `sessions` is the right tool when the set is decided
> per-request.

### Rules

Unlike the list endpoints above, this filter **fails closed**: an unrecognized
key or shape is rejected with `422` rather than ignored, because a silently
dropped filter here would widen recall instead of narrowing it.

| Rule | Behavior |
| - | - |
| Any key other than `session_id` | `422` |
| A shape other than a string, a list of strings, or `{"in": [...]}` | `422` |
| An entry that isn't a well-formed session id — wildcards included | `422` |
| More than 1,000 sessions | `422` |
| `session_id` set alongside `filters` | The `session_id` must appear in the allowlist, else `422` |
| An empty allowlist (`[]`) | Valid, and recalls nothing |
| A peer-scoped key naming a session its peer isn't an active member of | `401` on chat — see below |

> [!NOTE]
>
> On chat, a peer-scoped key must be an active member of every session it names —
> the allowlist reaches message recall there — and the request is rejected with
> `401` otherwise. The representation endpoint runs no membership check: key scope
> already confines the caller to its own peer's representation, which an allowlist
> can only narrow.

### What Changes Under an Allowlist

Restricting recall by session narrows what the reasoning agent can draw on:

* **Only `explicit` conclusions are recalled.** Dream-derived conclusions
  (`deductive`, `inductive`) are synthesized across sessions, so they can't be
  attributed to one session and are excluded.
* **Reasoning-chain traversal is unavailable**, since it walks into those
  derived conclusions.
* **Message recall is restricted to the allowlisted sessions** across every
  search path — semantic, keyword, and date-range.

> [!NOTE]
>
> Because of this, an allowlisted request answers from directly-stated facts
> rather than higher-order inferences. If you want the full representation, omit
> `filters` and let the agent search everything.

## Error Handling

Handle filter errors gracefully:

```python Python theme={null}
from honcho.exceptions import FilterError

try:
    # Invalid filter - unsupported operator
    messages = session.messages(filters={
        "created_at": {"invalid_operator": "2024-01-01"}
    })
except FilterError as e:
    print(f"Filter error: {e}")
    # Handle the error appropriately

try:
    # Invalid column name
    sessions = honcho.sessions(filters={
        "nonexistent_field": "value"
    })
except FilterError as e:
    print(f"Invalid field: {e}")
```

```typescript TypeScript theme={null}
(async () => {
    try {
      // Invalid filter - unsupported operator
      const messages = await session.messages({
        filters: {
          created_at: { invalid_operator: "2024-01-01" }
        }
      });
    } catch (error) {
      if (error.message.includes("filters")) {
        console.error(`Filter error: ${error.message}`);
        // Handle the error appropriately
      }
    }

    try {
      // Invalid column name
      const sessions = await honcho.sessions({
        filters: {
          nonexistent_field: "value"
        }
      });
    } catch (error) {
      console.error(`Invalid field: ${error.message}`);
    }
})();
```

## Conclusion

Honcho's filtering system provides powerful capabilities for querying your conversational data. By understanding how to:

* Use simple equality filters and complex logical operators
* Apply comparison operators for range and pattern matching
* Filter metadata with nested conditions
* Handle wildcards and dynamic filter construction
* Follow best practices for performance and validation

You can build sophisticated applications that efficiently find and process exactly the conversations, messages, and insights you need from your Honcho data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.