github →

New request

#21077: AI agent record search throws on LLM-generated filters (ilike on RICH_TEXT / operator at object level)

We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.

Issue
sonarly:medium

Summary

When an AI agent (Ask AI / agent chat) is asked to find or read records in a way that leads the model to filter on text, FindRecordsService passes the LLM-generated filter straight to the query runner, which throws in the filter arg processor. The error is caught (the turn recovers), but it wastes a tool call and makes text-search of RICH_TEXT fields (notes, about, bodyV2) impossible.

Environment

  • Self-hosted via Docker, twentycrm/twenty:latest (digest sha256:05799b9d…, pulled ~2026-05-19).
  • Provider: OpenAI, model openai/gpt-5.4 (smart default).
  • Default built-in Helper agent (it has record-search tools enabled).

Steps to reproduce

  1. Configure an AI provider and open Ask AI / agent chat.
  2. Ask something that makes the model search/read by text, e.g. "What are <Person>'s notes?"
  3. The agent locates the person, then attempts to filter on text fields.

Observed (twenty-server worker logs)

[ChatExecutionService] Starting chat execution with model openai/gpt-5.4, 5 active tools
[FindRecordsService] Found 1 records in person
ERROR [FindRecordsService] Failed to find records: Error: Object person doesn't have any "ilike" field.
ERROR [FindRecordsService] Failed to find records: Error: Object person doesn't have any "like" field.
ERROR [FindRecordsService] Failed to find records: Error: Sub field "ilike" not found for composite type: RICH_TEXT

Analysis

record-crud/services/find-records.service.jsFindRecordsService.execute forwards the LLM-provided filter to commonFindManyRunner.execute, and the throws originate in common-args-processors/filter-arg-processor. Two distinct invalid shapes the model produces:

  1. Operator at field position — e.g. { "ilike": "…" } placed at the filter root or directly inside and/or/not, where a field name is expected → "Object X doesn't have any 'ilike' field". Comparison operators can never legitimately appear at object level, so this is always invalid and safe to reject/strip.
  2. Text operator on a composite field — e.g. { notes: { ilike: "…" } } where notes/about/bodyV2 are RICH_TEXT composites → "Sub field 'ilike' not found for composite type: RICH_TEXT". This blocks any attempt to text-search rich fields.

Impact

Non-fatal (the catch returns { success: false } and the agent continues), but: a wasted tool round-trip (~15s / ~$0.10 observed on a single turn), and rich-text fields are effectively unsearchable by the agent.

Suggested fix

Sanitize / validate AI-tool filters by field type before execution (in FindRecordsService or, better, in the tool's filter schema so the model can't emit them):

  • Reject/strip comparison operators that appear at object-level positions (root, and/or/not children).
  • Restrict text operators (ilike, like) to scalar text fields; never apply them to composite/RICH_TEXT fields — or expose composite sub-fields appropriately.

Field metadata is already available in FindRecordsService.execute (flatFieldMetadataMaps from the context builder), so a type-aware sanitize is feasible at that layer. Alternatively, constrain the find tool's JSON schema / system guidance so the model doesn't generate these.

Assessmentadvisory
bug●● medium85% confidence

LLM-generated filters use object-level ilike and composite-field operators that the strict filter-arg processor rejects.

Likely files
  • packages/twenty-server/src/engine/core-modules/ai/services/find-records.service.ts
  • packages/twenty-server/src/engine/core-modules/ai/tools/record-search.tool.ts
  • packages/twenty-server/src/engine/api/rest/core/query-runner/helpers/process-graphql-args.helper.ts
Create the request

This opens a fresh agent run and a draft PR for issue #21077.

Cancel