Documentation
¶
Overview ¶
explain.go — the OFF-HOT-PATH dual of Fold: it folds the SAME adjudicator chain to the SAME winning verdict, but additionally records a per-rung Decision trace — the answer to the single most common debugging question in the whole kernel: "why did fak give THIS verdict for THIS tool call?"
Fold (the hot path) keeps only the winning verdict; every rung that ran, what each returned, and which one won are discarded. That is correct for the nanosecond budget of a served call, but it leaves `fak preflight` — the canonical 60-second proof command — printing a single opaque line (verdict=X reason=Y by=Z) with no way to see the eight rungs it actually folded. FoldExplain is the additive answer: callers that only need the verdict use Fold; callers answering "why" (fak preflight --explain/--json, the gateway decision trace, the agent run report) use this.
The Decision is deliberately SAFE TO LOG: it carries an args DIGEST and byte count, never the raw (possibly secret) args, and it surfaces only the bounded-disclosure witness the verdict already chose to disclose — the same deny-channel-is-not-a-policy-oracle discipline the adjudicator enforces.
Package kernel is the fak core: the one concrete implementation of abi.Kernel. It is a driver-blind integrator — it never imports a leaf package; it only WALKS the abi registries (Adjudicators, FastPaths, ResultAdmitters, engines, emitters). That is what lets the leaf subsystems be built in disjoint trees and linked in by a single blank-import line in internal/registrations.
The dispatch chain, in order:
Submit: vDSO FastPath lookup -> (miss) fold Adjudicator chain -> route verdict Reap: engine.Complete -> fold ResultAdmitter chain (context-MMU)
Adjudication happens entirely at Submit and touches neither the engine nor the network, so Submit's latency IS the tool-call adjudication latency the A/B benchmark reports. Reap carries the (slow, mockable) engine round-trip.
Index ¶
- Variables
- func DenyResult(c *abi.ToolCall, v abi.Verdict) *abi.Result
- func Disposition(r abi.ReasonCode) string
- func Fold(ctx context.Context, chain []abi.Adjudicator, c *abi.ToolCall) abi.Verdict
- type Counters
- type Decision
- type Kernel
- func (k *Kernel) AdmitResult(ctx context.Context, c *abi.ToolCall, r *abi.Result) abi.Verdict
- func (k *Kernel) BatchDecide(ctx context.Context, calls []*abi.ToolCall) []abi.Verdict
- func (k *Kernel) Counters() Counters
- func (k *Kernel) Decide(ctx context.Context, c *abi.ToolCall) abi.Verdict
- func (k *Kernel) Negotiate(advertised []abi.Capability) []abi.Capability
- func (k *Kernel) Reap(ctx context.Context, h abi.SubmissionHandle) (*abi.Result, error)
- func (k *Kernel) Resolver() abi.Resolver
- func (k *Kernel) SetVDSO(enabled bool)
- func (k *Kernel) Submit(ctx context.Context, c *abi.ToolCall) (abi.SubmissionHandle, abi.Verdict)
- func (k *Kernel) Syscall(ctx context.Context, c *abi.ToolCall) (*abi.Result, abi.Verdict)
- func (k *Kernel) VDSOEnabled() bool
- type Option
- type RungVerdict
Constants ¶
This section is empty.
Variables ¶
var ErrDenied = errors.New("kernel: call denied by adjudicator")
ErrDenied is returned by Reap for a call that adjudication refused.
Functions ¶
func DenyResult ¶
DenyResult builds the structured deny-as-value (unit 74): a Result carrying the reason token + derived disposition the next model turn consumes, with bounded witness disclosure (only the offending set, sourced from the verdict).
func Disposition ¶
func Disposition(r abi.ReasonCode) string
Disposition derives the deny-loopback disposition (RETRYABLE / WAIT / ESCALATE / TERMINAL) from the reason's category. Only MISROUTE is model-fixable (RETRYABLE); the rest steer the loop without another wasted model turn.
Types ¶
type Counters ¶
type Counters struct {
Submits int64
VDSOHits int64
EngineCalls int64
Denies int64
Transforms int64
Quarantines int64
Admitted int64
}
Counters are the kernel's call-path tallies (read by the metrics tap + tests).
type Decision ¶ added in v0.32.0
type Decision struct {
Tool string `json:"tool"`
ArgsDigest string `json:"args_digest,omitempty"` // sha256[:12] of the args bytes — never the raw args
ArgsBytes int `json:"args_bytes"` // size of the args payload
Verdict string `json:"verdict"` // final verdict kind name
Reason string `json:"reason,omitempty"` // final reason name (omitted when NONE)
By string `json:"by,omitempty"` // winning rung's By (or synthesized: empty-policy/all-defer)
Claim string `json:"claim,omitempty"` // final bounded-disclosure witness
Disposition string `json:"disposition,omitempty"` // deny loopback: RETRYABLE/WAIT/ESCALATE/TERMINAL
Posture string `json:"posture,omitempty"` // verdict Meta: e.g. admit_and_log
WouldDeny string `json:"would_deny,omitempty"` // verdict Meta: the reason a posture downgrade suppressed
Redacted []string `json:"redacted,omitempty"` // TRANSFORM: arg keys whose value the rung rewrote
Rungs []RungVerdict `json:"rungs"` // every rung consulted, in fold order
Explanation string `json:"explanation"` // one-line human summary
}
Decision is the full, explainable trace of one adjudication fold. It is the structured dual of the one-line verdict: every rung consulted, what each returned, which won, the bounded-disclosure witness, the loopback disposition, and a one-line human explanation. Built only off the hot path.
func FoldExplain ¶ added in v0.32.0
func FoldExplain(ctx context.Context, chain []abi.Adjudicator, c *abi.ToolCall) (abi.Verdict, Decision)
FoldExplain folds an Adjudicator chain EXACTLY as Fold does (same winning verdict, same lattice resolution) and additionally returns the per-rung Decision trace. The returned Verdict is byte-identical to Fold(ctx, chain, c): the trace is pure forensic surplus, never a behavior change.
type Kernel ¶
type Kernel struct {
// contains filtered or unexported fields
}
Kernel is the concrete abi.Kernel. Construct with New.
func New ¶
New builds a kernel bound to a registered engine id ("" => first/any engine, or a no-op engine if none registered). The Resolver comes from the registered RegionBackend (blob store in v0.1). Options (e.g. WithAdjudicators) are applied after construction; New with no options builds the v0.1 kernel unchanged.
func (*Kernel) AdmitResult ¶
AdmitResult runs a produced result through the ResultAdmitter chain (the result-side IFC containment + quarantine + per-trace taint ledger) and returns the resolved admission verdict. It is the EXPORTED dual of Decide (which folds only the pre-call chain): the gateway's served path calls it to arm the result-side stack on a result a CLIENT produced and handed back over the wire, so the exfil floor is no longer inert on the proxy/adjudicate topology. It has exactly the same effect on r as the in-process Reap path's admitResult.
func (*Kernel) BatchDecide ¶
BatchDecide vectorizes adjudication over a list of calls in one pass (unit 75, the "set" batch shape — dos-plan-price generalized). The result is, by construction, identical to deciding each call serially: it simply folds the same chain per call without a per-call kernel round-trip. The dual of speculative decoding — the expensive model proposes a plan, the cheap kernel prunes it in one pass.
func (*Kernel) Decide ¶
Decide folds ONLY the Adjudicator chain and returns the resolved Verdict. It touches no fast path, engine, or network — it is the pure in-process adjudication path the benchmark times against a spawned hook. Exported so the `fak hook` spawned-baseline mode and BenchmarkDecide share one code path.
func (*Kernel) Negotiate ¶
func (k *Kernel) Negotiate(advertised []abi.Capability) []abi.Capability
Negotiate intersects advertised caps with registered ones.
func (*Kernel) Reap ¶
Reap completes a submission. A fast-path result is returned directly. A denied call yields a structured deny Result (Status=Error, Meta carries reason + disposition) so the loop can consume it (deny-as-value). An allowed call is dispatched to the engine and the result run through the ResultAdmitter chain.
func (*Kernel) SetVDSO ¶
SetVDSO toggles the vDSO fast path (the --vdso on/off ablation, unit 5). When off, Submit never consults a FastPath, so every call falls through to adjudication + engine — the "off" arm of the A/B benchmark.
func (*Kernel) Submit ¶
Submit adjudicates and admits the call. The vDSO is consulted FIRST (unit 30); a hit returns immediately with no adjudication and no engine call. On a miss the Adjudicator chain is folded; a denied call is never enqueued.
func (*Kernel) Syscall ¶
Syscall is Submit then Reap (the synchronous convenience every caller uses).
func (*Kernel) VDSOEnabled ¶
VDSOEnabled reports whether the fast path is active.
type Option ¶ added in v0.32.0
type Option func(*Kernel)
Option configures a Kernel at construction (a functional option, additive: New with no options builds the v0.1 kernel unchanged).
func WithAdjudicators ¶ added in v0.32.0
func WithAdjudicators(chain []abi.Adjudicator) Option
WithAdjudicators injects an EXPLICIT adjudicator chain the kernel folds INSTEAD of the process-global abi.AdjudicatorsFor registry. It is the per-kernel adjudicator injection (issue #500): a replay driver can hand each of K policy arms its own monitor (e.g. []abi.Adjudicator{adjudicator.New(arm.Policy)}) and run them in parallel goroutines, with NO arm mutating the shared adjudicator.Default. The chain is folded per call through the same restrictiveness lattice and the same CallScope tool-scoping as the global path (via abi.ScopedFor), so a kernel given the global chain is verdict-identical to one reading the registry. Passing an empty/nil chain is a NO-OP — the kernel falls back to the global registry — so it can never silently install an empty (default-deny-everything) policy.
type RungVerdict ¶ added in v0.32.0
type RungVerdict struct {
Index int `json:"index"` // position in the folded chain (rank order)
Rung string `json:"rung"` // concrete adjudicator type, e.g. "adjudicator.Adjudicator"
By string `json:"by,omitempty"` // verdict.By: who claims the decision ("" on a bare defer)
Kind string `json:"kind"` // ALLOW/DENY/TRANSFORM/QUARANTINE/WITNESS/DEFER/...
Reason string `json:"reason,omitempty"` // reason name, omitted when NONE
Claim string `json:"claim,omitempty"` // bounded-disclosure witness, if this rung disclosed one
Rank int `json:"rank"` // FoldRank(kind): restrictiveness-lattice position
Deferred bool `json:"deferred"` // Kind==DEFER: this rung abstained, did not participate
Winner bool `json:"winner"` // this rung's verdict was the folded result
}
RungVerdict is one adjudicator's contribution to a folded decision — the per-rung detail Fold throws away. Rung is the concrete adjudicator type (a stable identity even when the rung DEFERS and sets no By), By is the verdict's self-reported decider, and Winner marks the rung whose verdict the lattice fold selected.