ask

package
v1.801.390 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package ask is a plain-language question about your business, answered with real numbers.

It is the UNIFIED GROUNDED ADVISOR behind POST /v1/ask. A founder asks a plain-language question ("what's my MRR?", "how long is my runway?") and gets an answer whose every figure is a REAL value read from a domain endpoint in-process — never a number the model invented.

ONE AND ONE WAY. /v1/ask is DISTINCT from /v1/chat/completions (the ai subsystem's RAW model completions) and from /v1/agent (the tool-calling orchestrator). Raw model → /v1/chat/completions; grounded advisor → /v1/ask. The advisor routes a question to the domain(s) that can ground it, reads the REAL figures from each domain's own endpoint in-process, hands the model the EXACT figures, and returns the grounded answer + the figures + the domain reads that backed them.

THE FLOW.

question → registry.Match (which domain grounds this?) → Contributor.Gather (replay the
         domain's grounded READ in-process, under the caller's OWN creds) → the REAL facts
         → narrate the facts with the model (prose only, never a number) → {answer, figures,
           followups, sources, domain}

GROUNDING CONTRACT (non-negotiable). Every figure in the answer is a real domain read; the model only NARRATES the figures it is handed and can never override one — the figures array is the Contributor's, computed BEFORE any model call and returned unaltered. If no domain can ground the question, the advisor says so honestly rather than guessing. Per-tenant isolation is inherited from the in-process replay carrying the caller's creds (agent.go's pattern): a question can only ever surface the caller's own org's data.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires POST /v1/ask into cloud, building the contributor registry (books today) over the SAME app so a contributor's Gather replays a domain's grounded read in-process. The narration model comes from deps.AI. Mount is a distinct route, so it wins Fiber's first-match over the ai /v1/* catch-all.

Types

type Contributor

type Contributor interface {
	Name() string
	CanAnswer(question string) bool
	Gather(ctx context.Context, cred map[string]string) (facts []Fact, sources []string, err error)
}

Contributor is one grounded domain behind the advisor. It is the ONE plug-in seam: a domain declares WHICH questions it can ground (CanAnswer) and HOW it reads the real figures (Gather), and the router composes it with no domain-specific branch of its own.

  • Name reports the domain id surfaced as the answer's `domain` and used in traces.
  • CanAnswer is the lightweight classifier: does this domain ground THIS question? Keyword/ intent today; an LLM classifier can replace the body without changing the seam.
  • Gather reads the REAL figures IN-PROCESS under the caller's own creds (the replayable header set), returning the facts and the domain reads (sources) that backed them. It never writes and never fabricates: an empty/zero domain yields honest zero figures.

type Fact

type Fact struct {
	Label  string `json:"label"`
	Value  string `json:"value"`
	Period string `json:"period,omitempty"`
}

Fact is one grounded figure a Contributor read from its domain: a label, its formatted value, and the period it covers. It is the exact {label,value,period?} shape the /v1/ask answer surfaces — the model narrates these, it never produces one.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the ordered set of contributors the router consults. It is populated once at Mount and read at request time, so adding a domain is a one-line append at the composition point — never a router edit.

func NewRegistry

func NewRegistry(cs ...Contributor) *Registry

NewRegistry builds the registry from the contributors wired at Mount, in priority order (first match wins the classification).

func (*Registry) Match

func (r *Registry) Match(question string) Contributor

Match returns the FIRST contributor that can ground the question, or nil when none can. It is the classification hook: deterministic first-match today (books is the sole domain), and the ONE place an LLM-based classifier or a multi-domain fan-out slots in later — the router only ever asks the registry "who can answer this?", never how the decision is made.

func (*Registry) Register

func (r *Registry) Register(c Contributor)

Register appends a contributor — the plug-in point a new domain calls to join the advisor without the router knowing it exists.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL