Curiosity

Search DSL

This is the reference for SearchRequest — the request shape used by every search call into a Curiosity Workspace, whether from the UI, a custom endpoint, or an external integration. For the conceptual model see Core concepts → Search model.

Request lifecycle

var request = new SearchRequest("MacBook Air")
    .WithTypesFacet("SupportCase", "Device")
    .WithFuzziness(1);

var result = await Graph.CreateSearchAsync(request);
return result;

The request describes what to find, the graph executes it, and a SearchResult comes back. There is no separate "execute" step — passing the request to CreateSearchAsync runs it.

Request fields

Query

Field Type Notes
Query.Value string The user-entered query string.
Query.Simple bool If true, disables operator parsing — treat the query as plain text.
Fuzziness int? Levenshtein distance allowed per term. Typical values: 1 or 2.
FilteredStopWords HashSet<string> Stop words to drop from this specific query.
Tag string Free-form label, surfaced in search analytics.
Tracker QueryTracker Optional analytics tracker; lets you tie hits to a session or feature.

Type scope

Field Type Notes
TypesFacet HashSet<string> Node types eligible as results.
BeforeTypesFacet HashSet<string> Applied earlier in the pipeline; narrows the candidate set before scoring. Use this when the scope is definitely known.
IgnoreNodeTypes HashSet<string> Types to exclude even if otherwise matched.
RestrictTo HashSet<string> Hard restriction list (intersect with whatever types match).
IncludeMergedOnTypeFacet bool If true, items merged via MergeRelated count toward the type facet.

BeforeTypesFacet is the more efficient option for "I always want only SupportCases here." TypesFacet is the right choice when the user is choosing the scope from the UI.

Target set

These restrict the search to nodes computed elsewhere — typically by a graph query — and are how you implement "search within context."

Field Type Notes
TargetUIDs ReadOnlyArray<UID128> Explicit UID list. Use when the set is small or precomputed.
TargetQuery SimpleQueryForUIDsOnly A graph query that resolves to UIDs at search time.
PreMergeTargetQuery SimpleQueryForUIDsOnly[] Target queries applied before related-merging logic runs.
ExcludeUIDs ReadOnlyArray<UID128> UIDs to remove from the result set.
ExcludeQuery SimpleQueryForUIDsOnly Graph-query equivalent of ExcludeUIDs.

Facets

Each facet kind is a Dictionary<string, …> keyed by field/edge name.

Field Type Use
Filters HashSet<UID128> Active filter UIDs (e.g. specific facet values).
RelatedFacets Dictionary<string, List<RelatedFacet>> Facet on an edge-traversed value (e.g. status).
NumericFacets Dictionary<string, List<NumericFacet>> Ranges on numeric properties.
TimeFacets Dictionary<string, TimeFacet> Time-window filters.
QueryFacets Dictionary<string, QueryFacet> Saved-query-based facets.
ValueFacets Dictionary<string, List<ValueFacet>> Single-value property filters.

Helper methods on SearchRequest (SetTimestampFacet, SetTimeFacet, …) wrap the dictionary form for common cases.

Sort and ranking

Field Type Notes
SortMode SortModeEnum Relevance, Recency, Connectivity, Custom, etc.
SortQuery SimpleQueryForUIDsOnly Custom sort defined as a graph query (for SortMode.Custom).
SimilarityRanking SimilarityRanking "More like this" / "less like that" boosting, optionally driven by graph queries.
TimeDecay TimeDecay Recency-bias scores (factor, offset, scale, top-N application).
Field Type Notes
VectorSearchTypes string[] Node types to vector-search. If unset, vector retrieval is skipped.
VectorSearchMode VectorSearchMode Hybrid (combine) or Only (vector results exclusively).
VectorSearchResults HashSet<UID128> Pre-computed vector hits (for client-driven hybrid flows).
Field Type Notes
MergeRelated Dictionary<string, MergeRelated> Collapse multiple hits that share an edge target.
ExpandRelated Dictionary<string, ExpandRelated> Inline related-node data into each hit.
SkipExpandMerge HashSet<string> Per-type opt-outs.

Behaviour flags

Field Type Notes
Lazy bool Defer expensive enrichment until the client asks for a specific hit.
SaveToHistory bool Default true; set to false for system-issued searches.
ShowSaveIfEmpty bool UI hint — show "save this search" when no results match.

Pagination

There is no Page/Offset/Limit on the request itself. Result-count control lives on the query that wraps the search, not the request:

var result = await Graph.CreateSearchAsync(request);
var page   = result.Results.Skip(50).Take(50);          // client-side window
// or, when chaining into a graph query:
var top    = Q().FromSearch(request).Skip(50).Take(50).Emit();

Workspace queries have a default limit of 50 nodes. If you want more, set .Take(n) on the wrapping query — there is no implicit upper bound but very large pages should use cursor-style pagination (carry the last-seen UID as ExcludeUIDs and re-query).

Response shape

public class SearchResult
{
    public SearchRequest             Request;
    public UID128                    QueryCache;
    public UID128                    SearchQueryUID;
    public string                    Count;
    public SearchStats               Stats;
    public ReadOnlyArray<SearchHit>  Results;
    public ReadOnlyArray<UID128>     ResultUIDs;
    public string                    TotalElapsedMilliseconds;
    public string                    Highlight;
    public bool                      Incomplete;
    public bool                      StillIndexing;
    public string                    ProCount;
}

SearchHit carries score, highlights, the resolved Node, child-hit pointers, and a FromVectorSearch flag so you can tell which retrieval branch produced the hit:

public class SearchHit
{
    public float        Score;
    public bool         Pinned;
    public bool         FromVectorSearch;
    public Node         Node;
    public TextAndUID[] Highlights;
    public UID128       SearchQueryUID;
    public ChildHit[]   ChildHits;
}

SearchStats gives you a per-request observability snapshot:

public class SearchStats
{
    public float                 ElapsedMilliseconds;
    public int                   TouchedNodes;
    public int                   TouchedEdges;
    public bool                  RestrictedNodesFilteredOut;
    public ReadOnlyArray<string> DetailedStats;
}

RestrictedNodesFilteredOut is true when ACL enforcement dropped hits before they reached the user — useful for distinguishing "no results" from "no results you can see." See Permission-aware retrieval.

Common patterns

Search within a graph context

var request = new SearchRequest("screen flicker")
    .WithTypesFacet("SupportCase")
    .WithTargetQuery(Q().StartAt("Manufacturer", "Apple").Out("Device").In("ForDevice"));

var result = await Graph.CreateSearchAsync(request);

Hybrid keyword + vector

var request = new SearchRequest("battery drains overnight")
    .WithTypesFacet("SupportCase");
request.VectorSearchTypes = new[] { "SupportCase" };
request.VectorSearchMode  = VectorSearchMode.Hybrid;

var result = await Graph.CreateSearchAsync(request);
var result = await Graph.CreateSearchAsUserAsync(request, userId);

CreateSearchAsUserAsync is the permission-aware variant — it applies the user's ACL graph before scoring. See Access control model.

Error semantics

The search endpoint surfaces failures as standard HTTP / token errors at the transport layer. Logical issues — unknown types, missing facet fields, ACL-filtered empties — come back as a normal SearchResult with Results.Length == 0 and signal:

  • Incomplete == true — the query was cut off (typically a timeout). Results are still valid but partial.
  • StillIndexing == true — at least one matching field is still being built. Re-run the query later for completeness.
  • RestrictedNodesFilteredOut == true — hits were removed by ACL.

For the catalog of error codes returned by REST/RPC calls (auth, schema, quota, etc.), see Error codes.

© 2026 Curiosity. All rights reserved.
Powered by Neko