Skip to content

Fuzzy search

Open in ChatGPT Open in Claude

searchInList() fuzzy-matches a query against a local array, with no network call. Use it inside action handlers to resolve a user’s loose phrasing (“the acme account”, “jon from billing”) to a concrete record before you build action parameters or feed data to a Task Agent.

In a conversational interface, users refer to records the way they remember them, not the way your database stores them. They type “acme” when the account is named “ACME Corporation Ltd”, “jon smth” when the user is “Jon Smith”, and they misspell both. An exact or substring lookup returns nothing for any of these, which forces the agent to come back with “no results” for a record that plainly exists.

Fuzzy matching closes that gap: it compares the query approximately, tolerating typos, partial words, and missing pieces. Each candidate gets a relevance score between 0 (perfect match) and 1 (barely related), and searchInList() drops anything weaker than 0.4 and returns the rest sorted best-first. Your handler then decides what to do with the confidence it has: auto-select a clear winner, or return the top candidates so the agent can ask the user to pick.

searchInList<T>(
items: T[],
query: string,
options: { searchKeys: string[] },
): { item: T; refIndex: number; score?: number }[];
ParameterTypeDescription
itemsT[]The list to search. Objects or primitives.
querystringThe search string, usually a user-supplied name or term.
options.searchKeysstring[]Property paths to match against each item, for example ["name", "id"].

An array of matches, sorted most-relevant first:

KeyTypeDescription
itemTThe matched element from items.
refIndexnumberIts index in the original items array.
scorenumberRelevance. 0 is a perfect match; lower is better. Present when a query is given.

Edge cases:

  • An empty or whitespace-only query returns every item in original order, with no score.
  • Empty items or empty searchKeys returns [].
  • Matches weaker than the built-in 0.4 threshold are dropped.

Match against every field a user might reference: name, domain, or ID in one call. Here “acme” resolves to the ACME Corporation record even though the user never typed the full name:

lookup-account.js
foldspace("when", "ready", () => {
const agent = foldspace.agent({ /* …common setup… */ });
const accounts = [
{ id: "acc-001", name: "ACME Corporation Ltd", domain: "acme.com", plan: "Enterprise" },
{ id: "acc-002", name: "Acmar Logistics", domain: "acmar.io", plan: "Pro" },
{ id: "acc-003", name: "Northwind Traders", domain: "northwind.com", plan: "Pro" },
];
agent.addActionHandlers({
lookup_account: {
execute: async (params) => {
const matches = agent
.searchInList(accounts, params.query, { searchKeys: ["name", "domain", "id"] })
.filter((r) => r.score < 0.4);
if (matches.length === 0) {
return { found: false, message: "No matching account." };
}
// A single confident hit: hand the agent the resolved record.
if (matches.length === 1 || matches[0].score < 0.2) {
return { found: true, account: matches[0].item };
}
// Ambiguous: return the candidates so the agent asks the user to pick.
return { found: true, candidates: matches.map((r) => r.item) };
},
},
});
});

The same pattern resolves people. “jon smth” matches “Jon Smith” despite the missing letters, and searching email too lets “jon@” or a partial address work:

const matches = agent
.searchInList(users, params.query, { searchKeys: ["name", "email"] })
.filter((r) => r.score < 0.4)
.map((r) => r.item);
const user = matches[0]; // best match, or undefined if nothing cleared the bar

Cheap exact or substring checks first, fuzzy searchInList() as the fallback. It is faster and avoids surprising matches when the user pasted an exact ID or email:

const exact = users.find(
(u) => u.email === query || u.name === query,
);
const match =
exact ??
agent
?.searchInList?.(users, query, { searchKeys: ["name", "email"] })
.filter((r) => r.score < 0.4)[0]?.item;

Optional-chain the call (agent?.searchInList?.(...)) when the agent may not be ready in the calling context.

The score is your confidence dial. Two thresholds cover most actions:

  • < 0.4: “plausible”. Use it to build a candidate list and let the agent confirm with the user.
  • < 0.2: “confident”. Use it to auto-select a single record without a confirmation round-trip, for example when “Q1 creators” should silently resolve to the “Q1 Creators 2026” list.
  • Task Agent API: feed resolved records into a background task.
  • Execute actions: write the execute handler this API is called from.
  • SDK APIs: the full programmatic surface of the agent instance.