Fency.ai
Sessions

Guardrails

Control which memories an agent can access for a task.

guardRails control which memories an agent can access. Your server sets them when creating a createAgentTask session. The React SDK only receives a clientToken and cannot expand the rails.

How guardRails apply per task type:

Task typeguardRails
EXPLORE_MEMORIESRequired. Both METADATA and SEMANTIC memory types are supported.
MEMORY_CHATRequired. Only SEMANTIC memory types are supported.
MEMORY_SEARCHRequired. Only SEMANTIC memory types are supported.
EXPLORE_PRODUCTOptional. Set them when the agent should perform semantic search on memories. The memory types used must be of type SEMANTIC.
STREAMING_CHAT_COMPLETIONNot supported
STRUCTURED_CHAT_COMPLETIONNot supported

How they work

Set guardRails.memoryTypes to a list of memory types the agent may see. Entries are ORed: the agent can read any memory that satisfies at least one entry.

Each entry uses exactly one memoryTypeId, and exactly one scope. Combining memoryIds, match, and includeAll on the same entry is rejected (400). Two entries with the same memoryTypeId are also rejected (400 Duplicate memory types found in guard rails).

ScopeWhat the agent can access
matchMemories of that type whose metadata matches the filter
memoryIdsOnly the listed memories of that type (max 100 Fency mem_... IDs)
includeAllEvery memory of that type

Use several entries when the agent should see more than one type at once, for example cars and the service records for those cars. Each type gets its own rail and its own scope. See Memories for why you typically create one memory type per database table.

Match rules

match filters on metadata you stamped when you created or updated the memory.

  • Keys in one match object are ANDed. A memory must satisfy every key.
  • Array values are ORed. A memory matches if its stored value is any one of the listed strings.
  • A memory that is missing the key never matches.
  • STRING keys compare equality (or membership when the rail value is an array).
  • STRING_LIST keys compare overlap: the memory matches if at least one stored value appears in the rail's list.
{
    memoryTypeId: 'mty_cars',
    match: {
        brand: 'volvo',
        lot_id: ['oslo', 'bergen'],
    },
}

This admits memories of that type that have brand equal to volvo and lot_id equal to oslo or bergen.

Because keys are ANDed, you cannot express "on lot oslo or this specific car" with two separate keys such as lot_id plus memoryIds. Use a single STRING_LIST field and OR the values. That pattern is below.

Combining lot, share, and seller access

A common access model is the union of:

  • every car on the user's lots
  • specific cars shared with the user
  • cars the user listed

You cannot combine match and memoryIds on the same rail, and you cannot add a second rail for the same memoryTypeId. memoryIds is also limited to 100 Fency IDs, so it does not scale to "every car shared with this user."

Stamp your own IDs on each memory as a STRING_LIST field (for example grants) and query that field with match. Prefixes such as lot:, car:, and seller: are a convention you choose, not fields Fency defines.

At index time, write every token that should grant access to that memory:

{
    memoryTypeId: 'mty_cars',
    sourceType: 'TEXT',
    title: '2022 Volvo XC40',
    text: '...',
    metadata: {
        grants: ['lot:oslo', 'car:car_1', 'seller:usr_ada'],
    },
}

A second car on another lot:

{
    memoryTypeId: 'mty_cars',
    sourceType: 'TEXT',
    title: '2019 Tesla Model 3',
    text: '...',
    metadata: {
        grants: ['lot:bergen', 'car:car_2', 'seller:usr_kai'],
    },
}

At session create, OR the tokens the current user is allowed to see:

{
    createAgentTask: {
        taskType: 'MEMORY_SEARCH',
        guardRails: {
            memoryTypes: [
                {
                    memoryTypeId: 'mty_cars',
                    match: {
                        grants: [
                            'lot:oslo',
                            'car:car_2',
                            'seller:usr_ada',
                        ],
                    },
                },
            ],
        },
    },
}

That query returns both memories: the first overlaps on lot:oslo (and seller:usr_ada), the second overlaps on car:car_2. A car that only has lot:trondheim does not match.

If you need AND inside one grant (for example "this seller's own listings on this lot only"), encode that as one token such as seller:usr_ada:lot:oslo and stamp it on those memories.

When a car moves lots or is shared with another seller, update the memory's metadata. You do not need to delete and recreate it.

Keep classification markers (sensitivity, PII) on their own STRING key. Overlap on a list is the right rule for ownership and sharing labels, not for "must not include this tag."

Metadata overrides

Each memory type entry can include a metadata array that overrides the stored meta-key schema for that session only:

  • visible: whether the agent can see that key.
  • description: the description the agent gets for that key, overriding the stored one.

Each entry needs a key plus at least one of visible or description.

chunkLimit

chunkLimit is set on the agent task (MemoryChat and MemorySearch), not on the session. It is applied after guard rails filter the corpus. You do not need to raise chunkLimit to compensate for memories the rails would discard.

Examples

match

{
    createAgentTask: {
        taskType: 'EXPLORE_MEMORIES',
        guardRails: {
            memoryTypes: [
                {
                    memoryTypeId: 'mty_...',
                    match: { organization_id: 'org_...' },
                    metadata: [
                        {
                            key: 'renewal_date',
                            visible: true,
                            description: 'ISO-8601 date the contract auto-renews',
                        },
                        {
                            key: 'internal_notes',
                            visible: false,
                        },
                    ],
                },
            ],
        },
    },
}

memoryIds

{
    createAgentTask: {
        taskType: 'MEMORY_CHAT',
        guardRails: {
            memoryTypes: [
                {
                    memoryTypeId: 'mty_...',
                    memoryIds: ['mem_...', 'mem_...'],
                    metadata: [
                        {
                            key: 'renewal_date',
                            visible: true,
                            description: 'ISO-8601 date the contract auto-renews',
                        },
                    ],
                },
            ],
        },
    },
}

includeAll

{
    createAgentTask: {
        taskType: 'MEMORY_SEARCH',
        guardRails: {
            memoryTypes: [
                {
                    memoryTypeId: 'mty_...',
                    includeAll: true,
                },
            ],
        },
    },
}

Two memory types (OR)

{
    createAgentTask: {
        taskType: 'MEMORY_CHAT',
        guardRails: {
            memoryTypes: [
                {
                    memoryTypeId: 'mty_cars',
                    match: { vin: 'vin_1' },
                },
                {
                    memoryTypeId: 'mty_service_records',
                    match: {
                        grants: ['car:vin_1'],
                    },
                },
            ],
        },
    },
}

See Sessions for full request bodies per task type, and Data exploration for a complete server route.

On this page