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.
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(digestsha256: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
- Configure an AI provider and open Ask AI / agent chat.
- Ask something that makes the model search/read by text, e.g. "What are <Person>'s notes?"
- 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.js → FindRecordsService.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:
- Operator at field position — e.g.
{ "ilike": "…" }placed at the filter root or directly insideand/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. - Text operator on a composite field — e.g.
{ notes: { ilike: "…" } }wherenotes/about/bodyV2areRICH_TEXTcomposites → "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/notchildren). - Restrict text operators (
ilike,like) to scalar text fields; never apply them to composite/RICH_TEXTfields — 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.
LLM-generated filters use object-level ilike and composite-field operators that the strict filter-arg processor rejects.
- 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