Documentation
¶
Overview ¶
Package fusion is the generic deterministic-fusion engine: fan-out sub-query dispatch, dedup, rank, and budget enforcement over a GraphQueryClient. It is a pure leaf package — no NATS, no research-domain types (Intent, ExecutionOutput, RouteAction). Domain-coupled callers (processor/research-graph-execute) compose this engine and wrap the result in their own payload types.
Index ¶
Constants ¶
const ( // DefaultMaxParallelism caps concurrent sub-query execution. // Keeps fan-out from saturating the responding gateways under // a wide-decompose scenario; operators can raise for staging // environments with more capacity. DefaultMaxParallelism = 8 // DefaultMaxResultsPerSubquery caps per-sub-query result count // before ranking + budget enforcement run. Prevents a single // runaway sub-query from dominating the evidence array. DefaultMaxResultsPerSubquery = 50 // DefaultBudgetTokens is the fallback evidence-budget when the // caller doesn't supply one. Should never normally fire when // callers supply a domain default, but acts as a safety net. DefaultBudgetTokens = 4000 // DefaultPerSubqueryTimeoutFraction is the fraction of the // overall context deadline each sub-query gets as its per-call // timeout. 0.5 leaves slack for fan-out overhead and ranking. DefaultPerSubqueryTimeoutFraction = 0.5 // DefaultExecuteTimeout is the fallback per-query timeout when // the caller's context has no deadline. Used only to bound // per-sub-query execution when ctx has no deadline set. DefaultExecuteTimeout = 60 * time.Second )
Default engine constants. Callers that pass zero-value FuseOptions fields fall through to these.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EntityStateArgs ¶
type EntityStateArgs struct {
EntityIDs []string `json:"entity_ids"`
}
EntityStateArgs carries IDs to fetch.
type Evidence ¶
type Evidence struct {
// EntityID is the graph entity this evidence refers to. Required.
EntityID string `json:"entity_id"`
// Tier is the retrieval tier that surfaced this hit: "0" (rules /
// predicate queries), "1" (BM25), or "2" (neural — deferred to
// Phase 2). Operators may use it to filter evidence by retrieval
// method.
Tier string `json:"tier"`
// Source is the retrieval source within the tier — e.g.
// "classifier", "predicate_walk", "bm25_index". Required.
Source string `json:"source"`
// Score is the within-tier ranking score (higher = better).
// Cross-tier comparison is not meaningful in Phase 1 (per-tier
// ordering + recency tie-break); Phase 2 may add a learned ranker.
Score float64 `json:"score,omitempty"`
// SnippetText is a short inline preview (prompt-injection
// friendly). Omit when the evidence has no readable preview.
SnippetText string `json:"snippet_text,omitempty"`
// ObjectStoreRef is the ObjectStore key for the full body when
// the evidence has bulk content. Empty when the evidence is fully
// expressed in the EntityID + triples on the graph.
ObjectStoreRef string `json:"objectstore_ref,omitempty"`
}
Evidence is one item in a fused evidence set — a single entity hit or evidence snippet the retrieval fan-out surfaced. Provenance is mandatory: every evidence item carries enough metadata (EntityID + Tier + Source) for the caller to verify it back against the graph and for downstream consumers (e.g. synthesize_answer's quote-back validation) to reject fabricated refs.
ObjectStoreRef is set when the body of the evidence (long text, document, etc.) lives in ObjectStore; consumers read it via that ref. SnippetText carries a short inline preview for prompt-injection readability without triggering ObjectStore round-trips on every hit.
type FuseOptions ¶
type FuseOptions struct {
// MaxParallelism caps concurrent sub-query execution. 0 uses DefaultMaxParallelism.
MaxParallelism int
// MaxResultsPerSubquery caps result count per sub-query before
// ranking + budget enforcement run. 0 uses DefaultMaxResultsPerSubquery.
MaxResultsPerSubquery int
// BudgetTokens is the estimated-token budget for the returned
// evidence set. 0 uses DefaultBudgetTokens.
BudgetTokens int
}
FuseOptions controls the engine's concurrency, per-subquery result cap, and evidence budget. Zero values fall through to the Default* constants.
type FuseResult ¶
type FuseResult struct {
// Evidence is the kept evidence set after dedup, sort, and budget
// enforcement. May be empty (degraded path or zero queries).
Evidence []Evidence
// Degraded flags incomplete fan-out — e.g. one or more sub-query
// errors or a timeout. The caller should surface this to operators
// or downstream LLM consumers as low-confidence input.
Degraded bool
// DegradedReason explains why Degraded is set. Multiple per-sub-query
// failures are joined with "; ".
DegradedReason string
// BudgetTokensUsed is the estimated token cost of the kept evidence
// array after budget enforcement. Useful for operator observability
// and tuning budget parameters.
BudgetTokensUsed int
}
FuseResult is the engine's output: dedup'd + ranked + budget-enforced evidence, plus observability fields.
func Fuse ¶
func Fuse(ctx context.Context, gq GraphQueryClient, queries []SubQuery, opts FuseOptions, logger *slog.Logger) (FuseResult, error)
Fuse runs the materialized sub-query set in parallel, deduplicates by EntityID (across tiers), sorts by tier + score + entity-ID tie-break, enforces the token budget, and returns a FuseResult.
gq must not be nil. An empty queries slice is not an error — it returns FuseResult{Degraded: true, DegradedReason: "..."}.
Per-sub-query failures are degrading, NOT chain-failing: they set FuseResult.Degraded and append to DegradedReason without cancelling sibling sub-queries. The only hard-fail is a parent-context cancellation, which returns a non-nil error.
The logger parameter is optional; nil uses slog.Default().
type GraphQueryClient ¶
type GraphQueryClient interface {
EntityState(ctx context.Context, args EntityStateArgs, tier, source string, limit int) ([]Evidence, error)
PredicateWalk(ctx context.Context, args PredicateWalkArgs, tier, source string, limit int) ([]Evidence, error)
TemporalRange(ctx context.Context, args TemporalRangeArgs, tier, source string, limit int) ([]Evidence, error)
BM25(ctx context.Context, args BM25Args, tier, source string, limit int) ([]Evidence, error)
}
GraphQueryClient is the narrow retrieval surface the fusion engine consumes. Production implementations wrap NATS-direct subjects; tests substitute in-memory fakes so the test matrix doesn't need a live graph stack.
Per-method semantics:
- EntityState: returns the current Triples for the named entities, projected into Evidence (one per entity).
- PredicateWalk: from each seed, returns Evidence for entities reachable within MaxHops via Predicates (empty Predicates = all).
- TemporalRange: returns Evidence for entities within the time window, optionally filtered by topic.
- BM25: text search via graph-query's existing BM25 surface.
All methods MUST stamp Tier + Source on every returned Evidence (taken from the SubQuery they were dispatched for) so provenance stays honest end-to-end. The engine does NOT re-stamp.
type PredicateWalkArgs ¶
type PredicateWalkArgs struct {
Seeds []string `json:"seeds"`
Predicates []string `json:"predicates,omitempty"`
MaxHops int `json:"max_hops,omitempty"`
}
PredicateWalkArgs carries seed IDs + optional predicate filter. MaxHops 0 resolves to 1 at the executor.
type SubQuery ¶
type SubQuery struct {
Type SubQueryType `json:"type"`
Tier string `json:"tier"` // "0" (predicate) or "1" (BM25)
Source string `json:"source"` // e.g. "walk_seeds:drone-001"; surfaces on Evidence.Source
// Per-type args. Exactly one is populated based on Type — the
// executor switch enforces. Optional fields allow tests + future
// templates to omit per-type primitives that don't apply.
EntityState *EntityStateArgs `json:"entity_state,omitempty"`
PredicateWalk *PredicateWalkArgs `json:"predicate_walk,omitempty"`
TemporalRange *TemporalRangeArgs `json:"temporal_range,omitempty"`
BM25 *BM25Args `json:"bm25,omitempty"`
}
SubQuery is a typed retrieval request. Each variant carries the minimum fields its tier executor needs; the Type discriminator drives dispatch in the engine's executeSubQuery.
Tier and Source travel with the SubQuery so the per-tier executor can stamp them onto every Evidence it produces without re-deriving — keeps provenance honest end-to-end.
type SubQueryType ¶
type SubQueryType string
SubQueryType is the closed set of Tier 0+1 primitives. Phase 2 adds spatial_polygon + neural; Phase 1 stays minimal.
const ( // SubQueryTypeEntityState fetches current state of named // entities. Tier 0; used for walk_seeds anchoring + decompose's // entity_type axis when classifier candidates are present. SubQueryTypeEntityState SubQueryType = "entity_state" // SubQueryTypePredicateWalk traverses predicate(s) from seed // entities. Tier 0; used for walk_seeds neighborhood expansion + // decompose's free-form axis fallback. SubQueryTypePredicateWalk SubQueryType = "predicate_walk" // SubQueryTypeTemporalRange queries entities within a time // window. Tier 0; used for decompose's time axis. Phase 1 ships // the type but the executor falls through to BM25 on topic when // graph-index-temporal isn't wired — operators see the // "temporal degrade" hint on the produced Evidence. SubQueryTypeTemporalRange SubQueryType = "temporal_range" // SubQueryTypeBM25 text-searches via graph-query's existing // BM25 surface. Tier 1; always added to widen coverage beyond // purely-structural retrieval (intent-shaped routing can miss // keyword matches the classifier surfaced). SubQueryTypeBM25 SubQueryType = "bm25" )
type TemporalRangeArgs ¶
type TemporalRangeArgs struct {
// Start / End are RFC3339 strings; timezone handling stays in the
// executor where the upstream graph-index-temporal surface lives.
Start string `json:"start"`
End string `json:"end"`
Topic string `json:"topic,omitempty"`
}
TemporalRangeArgs carries start/end + an optional topic filter. Empty Topic widens to all entities in the window (may be heavy; callers should always pass a topic in practice).