Documentation
¶
Overview ¶
Package decisions is kb's decision engine: one entry point (Engine.Decide) that owns transport to the decision model, receipt validation, the per-topic receipts cache, banding, budget, concurrency and run summaries.
Index ¶
- Constants
- Variables
- func CanonicalJSON(v any) ([]byte, error)
- func HashJSON(v any) (string, error)
- func IsOwnedKey(key string) bool
- func ParseCost(raw json.RawMessage) *float64
- func ReceiptsPath(topicRoot string) string
- func Redact(v any) any
- func RedactString(s string) string
- func SleepContext(ctx context.Context, d time.Duration) error
- type Answer
- type Band
- type Budget
- type Engine
- type GenerationSummary
- type Options
- type Purpose
- type PurposeSummary
- type Receipt
- type Receipts
- type Request
- type Result
- type Status
- type Summary
- type Thresholds
- type TopicRef
Constants ¶
const ( // DefaultAPIURL is OpenRouter's API base; the engine posts to // <api_url>/alpha/decisions. DefaultAPIURL = "https://openrouter.ai/api" // MaxQuestionsPerRequest is the batch size: larger question sets are split // into several requests over the same state. MaxQuestionsPerRequest = 48 // MaxStateTokens caps the estimated tokens of one state. The estimate is // bytes/4 of the canonical JSON (about four bytes per token for English // text and JSON punctuation); it only matters when max_state_bytes is // raised above 120 KB, and it keeps the state plus the longest question // under the model's 32k-token limit. MaxStateTokens = 30000 )
const ( RouteDecisions = "openrouter:alpha/decisions" RouteChatCompletions = "openrouter:v1/chat/completions" )
Routes recorded in receipts.
const ( ReasonTimeout = "timeout" ReasonRetries = "retries" ReasonBudget = "budget" ReasonInvalidReceipt = "invalid_receipt" ReasonStateTooLarge = "state_too_large" ReasonHTTPStatus = "http_status" ReasonMissingAnswer = "missing_answer" ReasonExcluded = "excluded" ReasonContextCanceled = "canceled" )
Undecided / not-checked reasons.
const ReceiptsDir = ".decisions"
ReceiptsDir is the per-topic directory holding decision records.
const ReceiptsFile = "receipts.jsonl"
ReceiptsFile is the append-only receipts log inside ReceiptsDir.
const Redacted = "[REDACTED]"
Redacted replaces every secret removed by Redact.
Variables ¶
var ( // ErrNotConfigured is returned by New when no OpenRouter API key is set. ErrNotConfigured = errors.New("decisions: OPENROUTER_API_KEY is not set; the decision model is required ([decisions] + [openrouter])") // ErrAuth marks a 401/402/403 from the decision endpoint. After the first // one, every later Decide returns it immediately. ErrAuth = errors.New("decisions: the decision endpoint refused the key or credits") // ErrInvalidRequest marks a request the engine cannot send: no or // duplicate question ids, malformed criteria, or a state that is not // JSON-serializable. It is a caller bug, not a model outcome. ErrInvalidRequest = errors.New("decisions: invalid request") )
var OwnedKeys = []string{
"triage", "triage_reason", "genre", "depth", "relevance", "concepts",
"summary", "criterion", "entities", "questions", "quality",
"ingest_batch", "ingest_query",
"related", "extends", "prerequisite", "example_of", "contradicts",
"affects", "supersedes", "aliases", "recaptured",
}
OwnedKeys are the frontmatter keys kb writes (spec §6). The writer, ingest's extra-frontmatter guard and lint all use this list. `locked` is user-owned and never written.
var RelationKeys = []string{"related", "extends", "prerequisite", "example_of", "contradicts"}
RelationKeys are the typed relation lists written by link.
Functions ¶
func CanonicalJSON ¶
CanonicalJSON encodes v as JSON with object keys sorted, numbers kept as written, no HTML escaping and no trailing newline, so equal values always hash equally. Cache keys and value hashes are computed over it.
func ParseCost ¶
func ParseCost(raw json.RawMessage) *float64
ParseCost reads a reported cost (a JSON number or numeric string). A missing, null, negative or non-numeric cost is unknown (nil), never zero.
func ReceiptsPath ¶
ReceiptsPath returns the receipts log path of a topic.
func Redact ¶
Redact returns a copy of v, as a generic JSON tree, with obvious secrets replaced by Redacted in every string value (API keys, bearer tokens, PEM private keys, `api_key=...` assignments) and in the string values of secret-looking keys (password, token, api_key, ...). A string in returns a string. A value that cannot be encoded as JSON is returned unchanged; the engine then rejects it as an invalid request.
func RedactString ¶
RedactString replaces obvious secrets in s with Redacted.
Types ¶
type Answer ¶
type Answer struct {
Type string `json:"type"`
Status Status `json:"status"`
Reason string `json:"reason,omitempty"`
Band Band `json:"band,omitempty"`
Noul *float64 `json:"noul,omitempty"`
Choice string `json:"choice,omitempty"`
Score *float64 `json:"score,omitempty"`
Probs map[string]float64 `json:"probabilities,omitempty"`
Confidence *float64 `json:"confidence,omitempty"`
// Receipt is the receipts key of the request that produced the answer
// (requests over 48 questions are split, so answers of one Result may
// come from different receipts).
Receipt string `json:"receipt,omitempty"`
}
Answer is one banded answer. Noul/Score/Confidence are pointers because absence differs from zero.
type Budget ¶
type Budget struct {
// contains filtered or unexported fields
}
Budget is a per-run spend ceiling in US$, shared by the decision engine and the generation client. It is safe for concurrent use. A nil *Budget is unlimited.
func NewBudget ¶
NewBudget returns a budget with the given ceiling. A ceiling of 0 allows no new calls, which makes a cache-only run.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine asks the decision model typed questions and returns banded answers. It is safe for concurrent use.
func New ¶
New builds an Engine. It returns ErrNotConfigured when opts.APIKey is empty and a validation error when opts.Config is unusable.
func (*Engine) Concurrency ¶
Concurrency returns the number of requests the engine runs at once, so callers can size their fan-out.
func (*Engine) Decide ¶
Decide asks every question of r against r.State and returns one banded answer per question id. It returns an error only for fatal conditions: ErrAuth (401/402/403, sticky for the engine), ctx cancellation, and ErrInvalidRequest. Every other failure is a per-answer status: undecided (timeout, retries, budget, invalid_receipt, http_status, missing_answer) or not_checked (state_too_large, or excluded: a Subject matching r.Topic.Exclude is answered without any call).
type GenerationSummary ¶
type GenerationSummary struct {
Calls int
CacheHits int
Fallbacks int
Failures int
CostUSD float64
CostUnknown int
}
GenerationSummary is the generation client's contribution to a run.
type Options ¶
type Options struct {
Config config.DecisionsConfig
APIKey string
APIURL string
HTTPClient *http.Client
// Budget is the run budget; nil creates one from Config.BudgetUSD.
Budget *Budget
// Receipts is the receipts store; nil creates one. Share it with the
// generation client through Engine.Receipts.
Receipts *Receipts
Now func() time.Time
// Sleep waits between retries; it must return ctx.Err() when ctx ends.
Sleep func(ctx context.Context, d time.Duration) error
Logger *slog.Logger
}
Options configure an Engine. Only Config and APIKey are required.
type Purpose ¶
type Purpose string
Purpose names why a decision is asked; it selects thresholds and groups the run summary.
const ( PurposeRelevance Purpose = "relevance" PurposeQuality Purpose = "quality" PurposeDuplicate Purpose = "duplicate" PurposeClassify Purpose = "classify" PurposeConcept Purpose = "concept" PurposeLink Purpose = "link" PurposeMention Purpose = "mention" PurposeFind Purpose = "find" PurposeOKFType Purpose = "okf_type" PurposeContractCheck Purpose = "contract_check" )
Purposes asked by kb.
type PurposeSummary ¶
type PurposeSummary struct {
Decided int
Bands map[Band]int
Undecided map[string]int // by reason
NotChecked map[string]int // by reason
}
PurposeSummary counts the answers of one purpose.
type Receipt ¶
type Receipt struct {
Key string `json:"key"`
Time string `json:"time"`
Purpose string `json:"purpose"`
Subject string `json:"subject"`
Bank string `json:"bank"`
BankVersion string `json:"bank_version"`
BankHash string `json:"bank_hash"`
Contract string `json:"contract"`
Model string `json:"model"`
ReportedModel string `json:"reported_model"`
Route string `json:"route"`
ResponseID string `json:"response_id,omitempty"`
Attempts int `json:"attempts"`
LatencyMS int64 `json:"latency_ms"`
InputTokens *int64 `json:"input_tokens"`
Cost *float64 `json:"cost"`
CostUnknown bool `json:"cost_unknown"`
Status Status `json:"status"`
Reason string `json:"reason,omitempty"`
Questions []string `json:"questions"`
Answers map[string]json.RawMessage `json:"answers"`
}
Receipt is one row of <topic>/.decisions/receipts.jsonl: one network call (one decision request, or one generation request). Answers hold the raw answers as returned by the model, never bands: bands are recomputed from the current thresholds on every read. Generation rows use purpose "generate:<kind>" and answers {"output": <json>}.
func LoadReceipts ¶
LoadReceipts reads every well-formed row of a topic's receipts log in file order. A missing log yields no rows; malformed lines (for example a line cut by a crash) are skipped. Readers that need one row per key take the last one.
type Receipts ¶
type Receipts struct {
// contains filtered or unexported fields
}
Receipts is the per-topic receipts log plus its in-memory cache. The engine and the generation client share one instance (Engine.Receipts) so appends to a topic's log are serialized. Each topic's log is loaded lazily, once; only rows with status decided serve as cache (last one per key wins). A topic with an empty root is cached in memory only.
type Request ¶
type Request struct {
Topic TopicRef
Purpose Purpose
Subject string
Bank *questions.Bank
State any
Questions []questions.Q
}
Request is one decision request: one state, many questions.
type Summary ¶
type Summary struct {
// Calls counts decision requests that reached the network.
Calls int
// CacheHits counts decision requests served from receipts.
CacheHits int
// CostUSD sums the reported cost of decision calls.
CostUSD float64
// CostUnknown counts decision calls with no reported cost (failed calls
// included); their spend is never assumed to be zero.
CostUnknown int
// Purposes holds per-purpose answer counts.
Purposes map[Purpose]PurposeSummary
// Generation is the generation client's part of the run.
Generation GenerationSummary
}
Summary is the run summary every command prints: calls, cache hits, cost, and per-purpose outcomes. Engine.Summary fills everything but Generation; callers that also generate set Generation from generation.Client.Summary before printing Lines.
type Thresholds ¶
Thresholds maps named thresholds (link_apply, relevance_quarantine, ...) to values. Missing names fall back to DefaultThresholds.
func DefaultThresholds ¶
func DefaultThresholds() Thresholds
DefaultThresholds are the measured defaults of spec §12.3.
func (Thresholds) For ¶
func (t Thresholds) For(purpose Purpose) (apply, review float64)
For returns the (apply, review) pair the engine bands a purpose's answers with. Noul answers are banded on P(yes); choice and score answers on confidence. Callers that gate on another quantity (P(off_topic), the sum of kept roles, the primary-concept probability, ...) call BandFor with the named threshold they need.
relevance relevance_quarantine / relevance_review quality quality_apply / quality_review duplicate duplicate / duplicate classify 0.5 / 0.5 (facets are written from the answer itself) concept concept_noul / primary_concept link link_apply / link_review mention mention_sense / link_review find find_keep / find_keep okf_type okf_type / 0.5 contract_check contract_conflict / contract_conflict anything else 0.8 / 0.5
func (Thresholds) Get ¶
func (t Thresholds) Get(name string) float64
Get returns the named threshold, falling back to the default.
func (Thresholds) Merge ¶
func (t Thresholds) Merge(override map[string]float64) Thresholds
Merge returns a copy of t with every entry of override applied.
type TopicRef ¶
type TopicRef struct {
Slug string
Root string
Contract string
Thresholds Thresholds
// Exclude are the topic.yaml `decisions.exclude` globs (topic-relative,
// `**`-aware): a request whose Subject matches one is never sent to any
// model (spec §14).
Exclude []string
}
TopicRef identifies the topic a decision belongs to: receipts live under Root/.decisions/, and Contract (hash) enters every cache key.