Fuzzy search
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.
Why fuzzy matching
Section titled “Why fuzzy matching”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.
Signature
Section titled “Signature”searchInList<T>( items: T[], query: string, options: { searchKeys: string[] },): { item: T; refIndex: number; score?: number }[];| Parameter | Type | Description |
|---|---|---|
items | T[] | The list to search. Objects or primitives. |
query | string | The search string, usually a user-supplied name or term. |
options.searchKeys | string[] | Property paths to match against each item, for example ["name", "id"]. |
Return value
Section titled “Return value”An array of matches, sorted most-relevant first:
| Key | Type | Description |
|---|---|---|
item | T | The matched element from items. |
refIndex | number | Its index in the original items array. |
score | number | Relevance. 0 is a perfect match; lower is better. Present when a query is given. |
Edge cases:
- An empty or whitespace-only
queryreturns every item in original order, with noscore. - Empty
itemsor emptysearchKeysreturns[]. - Matches weaker than the built-in
0.4threshold are dropped.
Resolve an account
Section titled “Resolve an account”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:
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) }; }, }, });});Resolve a user
Section titled “Resolve a user”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 barTry exact matches first
Section titled “Try exact matches first”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.
Pick a score cutoff
Section titled “Pick a score cutoff”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.
Related
Section titled “Related”- Task Agent API: feed resolved records into a background task.
- Execute actions: write the
executehandler this API is called from. - SDK APIs: the full programmatic surface of the agent instance.